cql-rb 1.0.0.pre0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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