cql-rb 1.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/cql/uuid.rb ADDED
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ class Uuid
5
+ def initialize(n)
6
+ case n
7
+ when String
8
+ @n = from_s(n)
9
+ else
10
+ @n = n
11
+ end
12
+ end
13
+
14
+ def to_s
15
+ @s ||= begin
16
+ parts = []
17
+ parts << (@n >> (24 * 4)).to_s(16).rjust(8, '0')
18
+ parts << ((@n >> (20 * 4)) & 0xffff).to_s(16).rjust(4, '0')
19
+ parts << ((@n >> (16 * 4)) & 0xffff).to_s(16).rjust(4, '0')
20
+ parts << ((@n >> (12 * 4)) & 0xffff).to_s(16).rjust(4, '0')
21
+ parts << (@n & 0xffffffffffff).to_s(16).rjust(12, '0')
22
+ parts.join('-').force_encoding(::Encoding::ASCII)
23
+ end
24
+ end
25
+
26
+ def value
27
+ @n
28
+ end
29
+
30
+ def eql?(other)
31
+ self.value == other.value
32
+ end
33
+ alias_method :==, :eql?
34
+
35
+ private
36
+
37
+ def from_s(str)
38
+ str = str.gsub('-', '')
39
+ n = 0
40
+ (str.length/2).times do |i|
41
+ n = (n << 8) | str[i * 2, 2].to_i(16)
42
+ end
43
+ n
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ VERSION = '1.0.0.pre0'.freeze
5
+ end
@@ -0,0 +1,368 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ describe Client do
8
+ let :connection_options do
9
+ {:host => 'example.com', :port => 12321, :io_reactor => io_reactor}
10
+ end
11
+
12
+ let :io_reactor do
13
+ FakeIoReactor.new
14
+ end
15
+
16
+ let :client do
17
+ described_class.new(connection_options)
18
+ end
19
+
20
+ def connections
21
+ io_reactor.connections
22
+ end
23
+
24
+ def connection
25
+ connections.first
26
+ end
27
+
28
+ def requests
29
+ connection[:requests]
30
+ end
31
+
32
+ def last_request
33
+ requests.last
34
+ end
35
+
36
+ describe '#start!' do
37
+ it 'connects' do
38
+ client.start!
39
+ connections.should have(1).item
40
+ end
41
+
42
+ it 'connects only once' do
43
+ client.start!
44
+ client.start!
45
+ connections.should have(1).item
46
+ end
47
+
48
+ it 'connects to all hosts' do
49
+ client.shutdown!
50
+ io_reactor.stop.get
51
+ io_reactor.start.get
52
+
53
+ c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
54
+ c.start!
55
+ connections.should have(3).items
56
+ end
57
+
58
+ it 'returns itself' do
59
+ client.start!.should equal(client)
60
+ end
61
+
62
+ it 'forwards the host and port' do
63
+ client.start!
64
+ connection[:host].should == 'example.com'
65
+ connection[:port].should == 12321
66
+ end
67
+
68
+ it 'sends a startup request' do
69
+ client.start!
70
+ last_request.should be_a(Protocol::StartupRequest)
71
+ end
72
+
73
+ it 'sends a startup request to each connection' do
74
+ client.shutdown!
75
+ io_reactor.stop.get
76
+ io_reactor.start.get
77
+
78
+ c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
79
+ c.start!
80
+ connections.each do |cc|
81
+ cc[:requests].last.should be_a(Protocol::StartupRequest)
82
+ end
83
+ end
84
+
85
+ it 'is not in a keyspace' do
86
+ client.start!
87
+ client.keyspace.should be_nil
88
+ end
89
+
90
+ it 'changes to the keyspace given as an option' do
91
+ c = described_class.new(connection_options.merge(:keyspace => 'hello_world'))
92
+ c.start!
93
+ last_request.should == Protocol::QueryRequest.new('USE hello_world', :one)
94
+ end
95
+
96
+ it 'validates the keyspace name before sending the USE command' do
97
+ c = described_class.new(connection_options.merge(:keyspace => 'system; DROP KEYSPACE system'))
98
+ expect { c.start! }.to raise_error(InvalidKeyspaceNameError)
99
+ requests.should_not include(Protocol::QueryRequest.new('USE system; DROP KEYSPACE system', :one))
100
+ end
101
+ end
102
+
103
+ describe '#shutdown!' do
104
+ it 'closes the connection' do
105
+ client.start!
106
+ client.shutdown!
107
+ io_reactor.should_not be_running
108
+ end
109
+
110
+ it 'accepts multiple calls to #shutdown!' do
111
+ client.start!
112
+ client.shutdown!
113
+ client.shutdown!
114
+ end
115
+
116
+ it 'returns itself' do
117
+ client.start!.shutdown!.should equal(client)
118
+ end
119
+ end
120
+
121
+ describe '#use' do
122
+ before do
123
+ client.start!
124
+ end
125
+
126
+ it 'executes a USE query' do
127
+ io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
128
+ client.use('system')
129
+ last_request.should == Protocol::QueryRequest.new('USE system', :one)
130
+ end
131
+
132
+ it 'executes a USE query for each connection' do
133
+ client.shutdown!
134
+ io_reactor.stop.get
135
+ io_reactor.start.get
136
+
137
+ c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
138
+ c.start!
139
+
140
+ c.use('system')
141
+ last_requests = connections.select { |c| c[:host] =~ /^h\d\.example\.com$/ }.sort_by { |c| c[:host] }.map { |c| c[:requests].last }
142
+ last_requests.should == [
143
+ Protocol::QueryRequest.new('USE system', :one),
144
+ Protocol::QueryRequest.new('USE system', :one),
145
+ Protocol::QueryRequest.new('USE system', :one)
146
+ ]
147
+ end
148
+
149
+ it 'knows which keyspace it changed to' do
150
+ io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
151
+ client.use('system')
152
+ client.keyspace.should == 'system'
153
+ end
154
+
155
+ it 'raises an error if the keyspace name is not valid' do
156
+ expect { client.use('system; DROP KEYSPACE system') }.to raise_error(InvalidKeyspaceNameError)
157
+ end
158
+ end
159
+
160
+ describe '#execute' do
161
+ before do
162
+ client.start!
163
+ end
164
+
165
+ it 'asks the connection to execute the query' do
166
+ client.execute('UPDATE stuff SET thing = 1 WHERE id = 3')
167
+ last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :quorum)
168
+ end
169
+
170
+ it 'uses the specified consistency' do
171
+ client.execute('UPDATE stuff SET thing = 1 WHERE id = 3', :three)
172
+ last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :three)
173
+ end
174
+
175
+ context 'with a void CQL query' do
176
+ it 'returns nil' do
177
+ io_reactor.queue_response(Protocol::VoidResultResponse.new)
178
+ result = client.execute('UPDATE stuff SET thing = 1 WHERE id = 3')
179
+ result.should be_nil
180
+ end
181
+ end
182
+
183
+ context 'with a USE query' do
184
+ it 'returns nil' do
185
+ io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
186
+ result = client.execute('USE system')
187
+ result.should be_nil
188
+ end
189
+
190
+ it 'knows which keyspace it changed to' do
191
+ io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
192
+ client.execute('USE system')
193
+ client.keyspace.should == 'system'
194
+ end
195
+
196
+ it 'detects that one connection changed to a keyspace and changes the others too' do
197
+ client.shutdown!
198
+ io_reactor.stop.get
199
+ io_reactor.start.get
200
+
201
+ c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
202
+ c.start!
203
+
204
+ io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h1.example.com' }[:host])
205
+ io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h2.example.com' }[:host])
206
+ io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h3.example.com' }[:host])
207
+
208
+ c.execute('USE system', :one)
209
+ c.keyspace.should == 'system'
210
+
211
+ last_requests = connections.select { |c| c[:host] =~ /^h\d\.example\.com$/ }.sort_by { |c| c[:host] }.map { |c| c[:requests].last }
212
+ last_requests.should == [
213
+ Protocol::QueryRequest.new('USE system', :one),
214
+ Protocol::QueryRequest.new('USE system', :one),
215
+ Protocol::QueryRequest.new('USE system', :one)
216
+ ]
217
+ end
218
+ end
219
+
220
+ context 'with an SELECT query' do
221
+ let :rows do
222
+ [['xyz', 'abc'], ['abc', 'xyz'], ['123', 'xyz']]
223
+ end
224
+
225
+ let :metadata do
226
+ [['thingies', 'things', 'thing', :text], ['thingies', 'things', 'item', :text]]
227
+ end
228
+
229
+ let :result do
230
+ io_reactor.queue_response(Protocol::RowsResultResponse.new(rows, metadata))
231
+ client.execute('SELECT * FROM things')
232
+ end
233
+
234
+ it 'returns an Enumerable of rows' do
235
+ row_count = 0
236
+ result.each do |row|
237
+ row_count += 1
238
+ end
239
+ row_count.should == 3
240
+ end
241
+
242
+ context 'with metadata that' do
243
+ it 'has keyspace, table and type information' do
244
+ result.metadata['item'].keyspace.should == 'thingies'
245
+ result.metadata['item'].table.should == 'things'
246
+ result.metadata['item'].column_name.should == 'item'
247
+ result.metadata['item'].type.should == :text
248
+ end
249
+
250
+ it 'is an Enumerable' do
251
+ result.metadata.map(&:type).should == [:text, :text]
252
+ end
253
+
254
+ it 'is splattable' do
255
+ ks, table, col, type = result.metadata['thing']
256
+ ks.should == 'thingies'
257
+ table.should == 'things'
258
+ col.should == 'thing'
259
+ type.should == :text
260
+ end
261
+ end
262
+ end
263
+
264
+ context 'when the response is an error' do
265
+ it 'raises an error' do
266
+ io_reactor.queue_response(Protocol::ErrorResponse.new(0xabcd, 'Blurgh'))
267
+ expect { client.execute('SELECT * FROM things') }.to raise_error(QueryError, 'Blurgh')
268
+ end
269
+ end
270
+ end
271
+
272
+ describe '#prepare' do
273
+ before do
274
+ client.start!
275
+ end
276
+
277
+ it 'sends a prepare request' do
278
+ client.prepare('SELECT * FROM system.peers')
279
+ last_request.should == Protocol::PrepareRequest.new('SELECT * FROM system.peers')
280
+ end
281
+
282
+ it 'returns a prepared statement' do
283
+ io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, [['stuff', 'things', 'item', :varchar]]))
284
+ statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?')
285
+ statement.should_not be_nil
286
+ end
287
+
288
+ it 'executes a prepared statement' do
289
+ id = 'A' * 32
290
+ metadata = [['stuff', 'things', 'item', :varchar]]
291
+ io_reactor.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
292
+ statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?')
293
+ statement.execute('foo')
294
+ last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['foo'], :quorum)
295
+ end
296
+
297
+ it 'executes a prepared statement using the right connection' do
298
+ client.shutdown!
299
+ io_reactor.stop.get
300
+ io_reactor.start.get
301
+
302
+ metadata = [['stuff', 'things', 'item', :varchar]]
303
+
304
+ c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
305
+ c.start!
306
+
307
+ io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, metadata))
308
+ io_reactor.queue_response(Protocol::PreparedResultResponse.new('B' * 32, metadata))
309
+ io_reactor.queue_response(Protocol::PreparedResultResponse.new('C' * 32, metadata))
310
+
311
+ statement1 = c.prepare('SELECT * FROM stuff.things WHERE item = ?')
312
+ statement1_connection = io_reactor.last_used_connection
313
+ statement2 = c.prepare('SELECT * FROM stuff.things WHERE item = ?')
314
+ statement2_connection = io_reactor.last_used_connection
315
+ statement3 = c.prepare('SELECT * FROM stuff.things WHERE item = ?')
316
+ statement3_connection = io_reactor.last_used_connection
317
+
318
+ io_reactor.queue_response(Protocol::RowsResultResponse.new([{'thing' => 'foo1'}], metadata), statement1_connection[:host])
319
+ io_reactor.queue_response(Protocol::RowsResultResponse.new([{'thing' => 'foo2'}], metadata), statement2_connection[:host])
320
+ io_reactor.queue_response(Protocol::RowsResultResponse.new([{'thing' => 'foo3'}], metadata), statement3_connection[:host])
321
+
322
+ statement1.execute('foo').first.should == {'thing' => 'foo1'}
323
+ statement2.execute('foo').first.should == {'thing' => 'foo2'}
324
+ statement3.execute('foo').first.should == {'thing' => 'foo3'}
325
+ end
326
+ end
327
+
328
+ context 'when not connected' do
329
+ it 'complains when #use is called before #start!' do
330
+ expect { client.use('system') }.to raise_error(NotConnectedError)
331
+ end
332
+
333
+ it 'complains when #use is called after #shutdown!' do
334
+ client.start!
335
+ client.shutdown!
336
+ expect { client.use('system') }.to raise_error(NotConnectedError)
337
+ end
338
+
339
+ it 'complains when #execute is called before #start!' do
340
+ expect { client.execute('DELETE FROM stuff WHERE id = 3') }.to raise_error(NotConnectedError)
341
+ end
342
+
343
+ it 'complains when #execute is called after #shutdown!' do
344
+ client.start!
345
+ client.shutdown!
346
+ expect { client.execute('DELETE FROM stuff WHERE id = 3') }.to raise_error(NotConnectedError)
347
+ end
348
+
349
+ it 'complains when #prepare is called before #start!' do
350
+ expect { client.prepare('DELETE FROM stuff WHERE id = 3') }.to raise_error(NotConnectedError)
351
+ end
352
+
353
+ it 'complains when #prepare is called after #shutdown!' do
354
+ client.start!
355
+ client.shutdown!
356
+ expect { client.prepare('DELETE FROM stuff WHERE id = 3') }.to raise_error(NotConnectedError)
357
+ end
358
+
359
+ it 'complains when #execute of a prepared statement is called after #shutdown!' do
360
+ client.start!
361
+ io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, []))
362
+ statement = client.prepare('DELETE FROM stuff WHERE id = 3')
363
+ client.shutdown!
364
+ expect { statement.execute }.to raise_error(NotConnectedError)
365
+ end
366
+ end
367
+ end
368
+ end
@@ -0,0 +1,297 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ describe Future do
8
+ let :future do
9
+ described_class.new
10
+ end
11
+
12
+ context 'when completed' do
13
+ it 'is complete' do
14
+ future.complete!('foo')
15
+ future.should be_complete
16
+ end
17
+
18
+ it 'has a value' do
19
+ future.complete!('foo')
20
+ future.value.should == 'foo'
21
+ end
22
+
23
+ it 'is complete even when the value is falsy' do
24
+ future.complete!(nil)
25
+ future.should be_complete
26
+ end
27
+
28
+ it 'returns the value from #get, too' do
29
+ future.complete!('foo')
30
+ future.get.should == 'foo'
31
+ end
32
+
33
+ it 'has the value nil by default' do
34
+ future.complete!
35
+ future.value.should be_nil
36
+ end
37
+
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'
45
+ end
46
+
47
+ it 'notifies new listeners even when already completed' do
48
+ v1, v2 = nil, nil
49
+ future.complete!('bar')
50
+ future.on_complete { |v| v1 = v }
51
+ future.on_complete { |v| v2 = v }
52
+ v1.should == 'bar'
53
+ v2.should == 'bar'
54
+ end
55
+
56
+ it 'blocks on #value until completed' do
57
+ Thread.start(future) do |f|
58
+ sleep 0.1
59
+ future.complete!('bar')
60
+ end
61
+ future.value.should == 'bar'
62
+ end
63
+
64
+ it 'blocks on #value until completed, when value is nil' do
65
+ Thread.start(future) do |f|
66
+ sleep 0.1
67
+ future.complete!
68
+ end
69
+ future.value.should be_nil
70
+ future.value.should be_nil
71
+ end
72
+
73
+ it 'blocks on #value until failed' do
74
+ Thread.start(future) do |f|
75
+ sleep 0.1
76
+ future.fail!(StandardError.new('FAIL!'))
77
+ end
78
+ expect { future.value }.to raise_error('FAIL!')
79
+ end
80
+
81
+ it 'cannot be completed again' do
82
+ future.complete!('bar')
83
+ expect { future.complete!('foo') }.to raise_error(FutureError)
84
+ end
85
+
86
+ it 'cannot be failed again' do
87
+ future.complete!('bar')
88
+ expect { future.fail!(StandardError.new('FAIL!')) }.to raise_error(FutureError)
89
+ end
90
+ end
91
+
92
+ context 'when failed' do
93
+ it 'is failed' do
94
+ future.fail!(StandardError.new('FAIL!'))
95
+ future.should be_failed
96
+ end
97
+
98
+ it 'raises the error from #value' do
99
+ future.fail!(StandardError.new('FAIL!'))
100
+ expect { future.value }.to raise_error('FAIL!')
101
+ end
102
+
103
+ it 'notifies all failure listeners' do
104
+ e1, e2 = nil, nil
105
+ future.on_failure { |e| e1 = e }
106
+ future.on_failure { |e| e2 = e }
107
+ future.fail!(StandardError.new('FAIL!'))
108
+ e1.message.should == 'FAIL!'
109
+ e2.message.should == 'FAIL!'
110
+ end
111
+
112
+ it 'notifies new listeners even when already failed' do
113
+ e1, e2 = nil, nil
114
+ future.fail!(StandardError.new('FAIL!'))
115
+ future.on_failure { |e| e1 = e }
116
+ future.on_failure { |e| e2 = e }
117
+ e1.message.should == 'FAIL!'
118
+ e2.message.should == 'FAIL!'
119
+ end
120
+
121
+ it 'cannot be failed again' do
122
+ future.fail!(StandardError.new('FAIL!'))
123
+ expect { future.fail!(StandardError.new('FAIL!')) }.to raise_error(FutureError)
124
+ end
125
+
126
+ it 'cannot be completed' do
127
+ future.fail!(StandardError.new('FAIL!'))
128
+ expect { future.complete!('hurgh') }.to raise_error(FutureError)
129
+ end
130
+ end
131
+
132
+ describe '.combine' do
133
+ context 'returns a new future which' do
134
+ it 'is complete when the source futures are complete' do
135
+ f1 = Future.new
136
+ f2 = Future.new
137
+ f3 = Future.combine(f1, f2)
138
+ f1.complete!
139
+ f3.should_not be_complete
140
+ f2.complete!
141
+ f3.should be_complete
142
+ end
143
+
144
+ it 'completes when the source futures have completed' do
145
+ sequence = []
146
+ f1 = Future.new
147
+ f2 = Future.new
148
+ f3 = Future.new
149
+ f4 = Future.combine(f1, f2, f3)
150
+ f1.on_complete { sequence << 1 }
151
+ f2.on_complete { sequence << 2 }
152
+ f3.on_complete { sequence << 3 }
153
+ f2.complete!
154
+ f1.complete!
155
+ f3.complete!
156
+ sequence.should == [2, 1, 3]
157
+ end
158
+
159
+ it 'returns an array of the values of the source futures, in order' do
160
+ f1 = Future.new
161
+ f2 = Future.new
162
+ f3 = Future.new
163
+ f4 = Future.combine(f1, f2, f3)
164
+ f2.complete!(2)
165
+ f1.complete!(1)
166
+ f3.complete!(3)
167
+ f4.get.should == [1, 2, 3]
168
+ end
169
+
170
+ it 'fails if any of the source futures fail' do
171
+ f1 = Future.new
172
+ f2 = Future.new
173
+ f3 = Future.new
174
+ f4 = Future.new
175
+ f5 = Future.combine(f1, f2, f3, f4)
176
+ f2.complete!
177
+ f1.fail!(StandardError.new('hurgh'))
178
+ f3.fail!(StandardError.new('murgasd'))
179
+ f4.complete!
180
+ expect { f5.get }.to raise_error('hurgh')
181
+ f5.should be_failed
182
+ end
183
+
184
+ it 'raises an error when #complete! is called' do
185
+ f = Future.combine(Future.new, Future.new)
186
+ expect { f.complete! }.to raise_error(FutureError)
187
+ end
188
+
189
+ it 'raises an error when #fail! is called' do
190
+ f = Future.combine(Future.new, Future.new)
191
+ expect { f.fail!(StandardError.new('Blurgh')) }.to raise_error(FutureError)
192
+ end
193
+ end
194
+ end
195
+
196
+ describe '#map' do
197
+ context 'returns a new future that' do
198
+ it 'will complete with the result of the given block' do
199
+ mapped_value = nil
200
+ f1 = Future.new
201
+ f2 = f1.map { |v| v * 2 }
202
+ f2.on_complete { |v| mapped_value = v }
203
+ f1.complete!(3)
204
+ mapped_value.should == 3 * 2
205
+ end
206
+
207
+ it 'fails when the original future fails' do
208
+ failed = false
209
+ f1 = Future.new
210
+ f2 = f1.map { |v| v * 2 }
211
+ f2.on_failure { failed = true }
212
+ f1.fail!(StandardError.new('Blurgh'))
213
+ failed.should be_true
214
+ end
215
+
216
+ it 'fails when the block raises an error' do
217
+ f1 = Future.new
218
+ f2 = f1.map { |v| raise 'Blurgh' }
219
+ Thread.start do
220
+ sleep(0.01)
221
+ f1.complete!
222
+ end
223
+ expect { f2.get }.to raise_error('Blurgh')
224
+ end
225
+ end
226
+ end
227
+
228
+ describe '#flat_map' do
229
+ it 'works like #map, but expects that the block returns a future' do
230
+ f1 = Future.new
231
+ f2 = f1.flat_map { |v| Future.completed(v * 2) }
232
+ f1.complete!(3)
233
+ f2.value.should == 3 * 2
234
+ end
235
+
236
+ it 'fails when the block raises an error' do
237
+ f1 = Future.new
238
+ f2 = f1.flat_map { |v| raise 'Hurgh' }
239
+ f1.complete!(3)
240
+ expect { f2.get }.to raise_error('Hurgh')
241
+ end
242
+ end
243
+
244
+ describe '.completed' do
245
+ let :future do
246
+ described_class.completed('hello world')
247
+ end
248
+
249
+ it 'is complete when created' do
250
+ future.should be_complete
251
+ end
252
+
253
+ it 'calls callbacks immediately' do
254
+ value = nil
255
+ future.on_complete { |v| value = v }
256
+ value.should == 'hello world'
257
+ end
258
+
259
+ it 'does not block on #value' do
260
+ future.value.should == 'hello world'
261
+ end
262
+
263
+ it 'defaults to the value nil' do
264
+ described_class.completed.value.should be_nil
265
+ end
266
+
267
+ it 'handles #map' do
268
+ described_class.completed('foo').map(&:upcase).value.should == 'FOO'
269
+ end
270
+
271
+ it 'handles #map' do
272
+ f = described_class.completed('foo').map { |v| raise 'Blurgh' }
273
+ f.should be_failed
274
+ end
275
+ end
276
+
277
+ describe '.failed' do
278
+ let :future do
279
+ described_class.failed(StandardError.new('Blurgh'))
280
+ end
281
+
282
+ it 'is failed when created' do
283
+ future.should be_failed
284
+ end
285
+
286
+ it 'calls callbacks immediately' do
287
+ error = nil
288
+ future.on_failure { |e| error = e }
289
+ error.message.should == 'Blurgh'
290
+ end
291
+
292
+ it 'does not block on #value' do
293
+ expect { future.value }.to raise_error('Blurgh')
294
+ end
295
+ end
296
+ end
297
+ end