cql-rb 1.0.6 → 1.1.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README.md +4 -9
  2. data/lib/cql.rb +1 -0
  3. data/lib/cql/byte_buffer.rb +23 -7
  4. data/lib/cql/client.rb +11 -6
  5. data/lib/cql/client/asynchronous_client.rb +37 -83
  6. data/lib/cql/client/asynchronous_prepared_statement.rb +10 -4
  7. data/lib/cql/client/column_metadata.rb +16 -0
  8. data/lib/cql/client/request_runner.rb +46 -0
  9. data/lib/cql/future.rb +4 -5
  10. data/lib/cql/io.rb +2 -5
  11. data/lib/cql/io/connection.rb +220 -0
  12. data/lib/cql/io/io_reactor.rb +213 -185
  13. data/lib/cql/protocol.rb +1 -0
  14. data/lib/cql/protocol/cql_protocol_handler.rb +201 -0
  15. data/lib/cql/protocol/decoding.rb +6 -31
  16. data/lib/cql/protocol/encoding.rb +1 -5
  17. data/lib/cql/protocol/request.rb +4 -0
  18. data/lib/cql/protocol/responses/schema_change_result_response.rb +15 -0
  19. data/lib/cql/protocol/type_converter.rb +56 -76
  20. data/lib/cql/time_uuid.rb +104 -0
  21. data/lib/cql/uuid.rb +4 -2
  22. data/lib/cql/version.rb +1 -1
  23. data/spec/cql/client/asynchronous_client_spec.rb +47 -71
  24. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +68 -0
  25. data/spec/cql/client/client_shared.rb +3 -3
  26. data/spec/cql/client/column_metadata_spec.rb +80 -0
  27. data/spec/cql/client/request_runner_spec.rb +120 -0
  28. data/spec/cql/future_spec.rb +26 -11
  29. data/spec/cql/io/connection_spec.rb +460 -0
  30. data/spec/cql/io/io_reactor_spec.rb +212 -265
  31. data/spec/cql/protocol/cql_protocol_handler_spec.rb +216 -0
  32. data/spec/cql/protocol/decoding_spec.rb +9 -28
  33. data/spec/cql/protocol/encoding_spec.rb +0 -5
  34. data/spec/cql/protocol/request_spec.rb +16 -0
  35. data/spec/cql/protocol/response_frame_spec.rb +2 -2
  36. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +70 -0
  37. data/spec/cql/time_uuid_spec.rb +136 -0
  38. data/spec/cql/uuid_spec.rb +1 -5
  39. data/spec/integration/client_spec.rb +34 -38
  40. data/spec/integration/io_spec.rb +283 -0
  41. data/spec/integration/protocol_spec.rb +53 -113
  42. data/spec/integration/regression_spec.rb +124 -0
  43. data/spec/integration/uuid_spec.rb +76 -0
  44. data/spec/spec_helper.rb +12 -9
  45. data/spec/support/fake_io_reactor.rb +52 -21
  46. data/spec/support/fake_server.rb +2 -2
  47. metadata +33 -10
  48. checksums.yaml +0 -15
  49. data/lib/cql/io/node_connection.rb +0 -209
  50. data/spec/cql/protocol/type_converter_spec.rb +0 -52
@@ -13,12 +13,12 @@ shared_context 'client setup' do
13
13
  io_reactor.connections
14
14
  end
15
15
 
16
- def connection
17
- connections.first
16
+ def last_connection
17
+ connections.last
18
18
  end
19
19
 
20
20
  def requests
21
- connection[:requests]
21
+ last_connection.requests
22
22
  end
23
23
 
24
24
  def last_request
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ module Client
8
+ describe ColumnMetadata do
9
+ describe '#eql?' do
10
+ it 'is equal to another column metadata with the same properties' do
11
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
12
+ cm2 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
13
+ cm1.should eql(cm2)
14
+ end
15
+
16
+ it 'is aliased as #==' do
17
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
18
+ cm2 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
19
+ cm1.should == cm2
20
+ end
21
+
22
+ it 'is not equal to another column metadata when the keyspace names differ' do
23
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
24
+ cm2 = ColumnMetadata.new('another_keyspace', 'my_table', 'my_column', :uuid)
25
+ cm1.should_not eql(cm2)
26
+ end
27
+
28
+ it 'is not equal to another column metadata when the table names differ' do
29
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
30
+ cm2 = ColumnMetadata.new('my_keyspace', 'another_table', 'my_column', :uuid)
31
+ cm1.should_not eql(cm2)
32
+ end
33
+
34
+ it 'is not equal to another column metadata when the column names differ' do
35
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
36
+ cm2 = ColumnMetadata.new('my_keyspace', 'my_table', 'another_column', :uuid)
37
+ cm1.should_not eql(cm2)
38
+ end
39
+
40
+ it 'is not equal to another column metadata when the types differ' do
41
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
42
+ cm2 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :timestamp)
43
+ cm1.should_not eql(cm2)
44
+ end
45
+ end
46
+
47
+ describe '#hash' do
48
+ it 'is the same to another column metadata with the same properties' do
49
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
50
+ cm2 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
51
+ cm1.hash.should == cm2.hash
52
+ end
53
+
54
+ it 'is not the same when the keyspace names differ' do
55
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
56
+ cm2 = ColumnMetadata.new('another_keyspace', 'my_table', 'my_column', :uuid)
57
+ cm1.hash.should_not == cm2.hash
58
+ end
59
+
60
+ it 'is not the same when the table names differ' do
61
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
62
+ cm2 = ColumnMetadata.new('my_keyspace', 'another_table', 'my_column', :uuid)
63
+ cm1.hash.should_not == cm2.hash
64
+ end
65
+
66
+ it 'is not the same when the column names differ' do
67
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
68
+ cm2 = ColumnMetadata.new('my_keyspace', 'my_table', 'another_column', :uuid)
69
+ cm1.hash.should_not == cm2.hash
70
+ end
71
+
72
+ it 'is not the same when the types differ' do
73
+ cm1 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :uuid)
74
+ cm2 = ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :timestamp)
75
+ cm1.hash.should_not == cm2.hash
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ module Client
8
+ describe RequestRunner do
9
+ let :runner do
10
+ described_class.new
11
+ end
12
+
13
+ let :connection do
14
+ stub(:connection)
15
+ end
16
+
17
+ let :request do
18
+ stub(:request)
19
+ end
20
+
21
+ let :metadata do
22
+ [
23
+ ['my_keyspace', 'my_table', 'my_column', :int],
24
+ ['my_keyspace', 'my_table', 'my_other_column', :text],
25
+ ]
26
+ end
27
+
28
+ let :rows do
29
+ [
30
+ {'my_column' => 11, 'my_other_column' => 'hello'},
31
+ {'my_column' => 22, 'my_other_column' => 'foo'},
32
+ {'my_column' => 33, 'my_other_column' => 'bar'},
33
+ ]
34
+ end
35
+
36
+ describe '#execute' do
37
+ let :rows_response do
38
+ Protocol::RowsResultResponse.new(rows, metadata)
39
+ end
40
+
41
+ let :void_response do
42
+ Protocol::VoidResultResponse.new
43
+ end
44
+
45
+ let :prepared_response do
46
+ Protocol::PreparedResultResponse.new("\x2a", metadata)
47
+ end
48
+
49
+ let :error_response do
50
+ Protocol::ErrorResponse.new(0xbad, 'Bork')
51
+ end
52
+
53
+ let :authenticate_response do
54
+ Protocol::AuthenticateResponse.new('TheAuthenticator')
55
+ end
56
+
57
+ let :set_keyspace_response do
58
+ Protocol::SetKeyspaceResultResponse.new('some_keyspace')
59
+ end
60
+
61
+ def run(response, rq=request)
62
+ connection.stub(:send_request).and_return(Future.completed(response))
63
+ runner.execute(connection, rq).get
64
+ end
65
+
66
+ it 'executes the request' do
67
+ connection.should_receive(:send_request).and_return(Future.completed(rows_response))
68
+ runner.execute(connection, request)
69
+ end
70
+
71
+ it 'transforms a RowsResultResponse to a query result' do
72
+ result = run(rows_response)
73
+ result.should have(3).items
74
+ end
75
+
76
+ it 'transforms a VoidResultResponse to nil' do
77
+ result = run(void_response)
78
+ result.should be_nil
79
+ end
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
+ it 'transforms a AuthenticateResponse to an authentication required object' do
88
+ result = run(authenticate_response)
89
+ result.should be_a(AuthenticationRequired)
90
+ result.authentication_class.should == 'TheAuthenticator'
91
+ end
92
+
93
+ it 'transforms a SetKeyspaceResultResponse into a keyspace changed object' do
94
+ result = run(set_keyspace_response)
95
+ result.should be_a(KeyspaceChanged)
96
+ result.keyspace.should == 'some_keyspace'
97
+ end
98
+
99
+ it 'intercepts an ErrorResponse and fails the result future' do
100
+ expect { run(error_response) }.to raise_error(QueryError)
101
+ end
102
+
103
+ it 'sets the #cql field of QueryError when the request is a query request' do
104
+ begin
105
+ run(error_response, Protocol::QueryRequest.new('SELECT * FROM everything', :all))
106
+ rescue QueryError => e
107
+ e.cql.should == 'SELECT * FROM everything'
108
+ else
109
+ fail!('No error was raised')
110
+ end
111
+ end
112
+
113
+ it 'transforms all other responses to nil' do
114
+ result = run('hibbly hobbly')
115
+ result.should be_nil
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -44,6 +44,14 @@ module Cql
44
44
  v2.should == 'bar'
45
45
  end
46
46
 
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'
53
+ end
54
+
47
55
  it 'notifies new listeners even when already completed' do
48
56
  v1, v2 = nil, nil
49
57
  future.complete!('bar')
@@ -53,6 +61,11 @@ module Cql
53
61
  v2.should == 'bar'
54
62
  end
55
63
 
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
67
+ end
68
+
56
69
  it 'blocks on #value until completed' do
57
70
  Thread.start(future) do |f|
58
71
  sleep 0.1
@@ -78,17 +91,6 @@ module Cql
78
91
  expect { future.value }.to raise_error('FAIL!')
79
92
  end
80
93
 
81
- it 'allows multiple thread to block on #value until completed' do
82
- listeners = Array.new(10) do
83
- Thread.start do
84
- future.value
85
- end
86
- end
87
- sleep 0.1
88
- future.complete!(:hello)
89
- listeners.map(&:value).should == Array.new(10, :hello)
90
- end
91
-
92
94
  it 'cannot be completed again' do
93
95
  future.complete!('bar')
94
96
  expect { future.complete!('foo') }.to raise_error(FutureError)
@@ -120,6 +122,14 @@ module Cql
120
122
  e2.message.should == 'FAIL!'
121
123
  end
122
124
 
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!'
131
+ end
132
+
123
133
  it 'notifies new listeners even when already failed' do
124
134
  e1, e2 = nil, nil
125
135
  future.fail!(StandardError.new('FAIL!'))
@@ -129,6 +139,11 @@ module Cql
129
139
  e2.message.should == 'FAIL!'
130
140
  end
131
141
 
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
145
+ end
146
+
132
147
  it 'cannot be failed again' do
133
148
  future.fail!(StandardError.new('FAIL!'))
134
149
  expect { future.fail!(StandardError.new('FAIL!')) }.to raise_error(FutureError)
@@ -0,0 +1,460 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ module Io
8
+ describe Connection do
9
+ let :handler do
10
+ described_class.new('example.com', 55555, 5, unblocker, socket_impl, clock)
11
+ end
12
+
13
+ let :unblocker do
14
+ stub(:unblocker, unblock!: nil)
15
+ end
16
+
17
+ let :socket_impl do
18
+ stub(:socket_impl)
19
+ end
20
+
21
+ let :clock do
22
+ stub(:clock, now: 0)
23
+ end
24
+
25
+ let :socket do
26
+ stub(:socket)
27
+ end
28
+
29
+ before do
30
+ socket_impl.stub(:getaddrinfo)
31
+ .with('example.com', 55555, nil, Socket::SOCK_STREAM)
32
+ .and_return([[nil, 'PORT', nil, 'IP1', 'FAMILY1', 'TYPE1'], [nil, 'PORT', nil, 'IP2', 'FAMILY2', 'TYPE2']])
33
+ socket_impl.stub(:sockaddr_in)
34
+ .with('PORT', 'IP1')
35
+ .and_return('SOCKADDR1')
36
+ socket_impl.stub(:new)
37
+ .with('FAMILY1', 'TYPE1', 0)
38
+ .and_return(socket)
39
+ end
40
+
41
+ describe '#connect' do
42
+ it 'creates a socket and calls #connect_nonblock' do
43
+ socket.should_receive(:connect_nonblock).with('SOCKADDR1')
44
+ handler.connect
45
+ end
46
+
47
+ it 'handles EINPROGRESS that #connect_nonblock raises' do
48
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
49
+ handler.connect
50
+ end
51
+
52
+ it 'is connecting after #connect has been called' do
53
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
54
+ handler.connect
55
+ handler.should be_connecting
56
+ end
57
+
58
+ it 'is connecting even after the second call' do
59
+ socket.should_receive(:connect_nonblock).twice.and_raise(Errno::EINPROGRESS)
60
+ handler.connect
61
+ handler.connect
62
+ handler.should be_connecting
63
+ end
64
+
65
+ it 'does not create a new socket the second time' do
66
+ socket_impl.should_receive(:new).once.and_return(socket)
67
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
68
+ handler.connect
69
+ handler.connect
70
+ end
71
+
72
+ it 'attempts another connect the second time' do
73
+ socket.should_receive(:connect_nonblock).twice.and_raise(Errno::EINPROGRESS)
74
+ handler.connect
75
+ handler.connect
76
+ end
77
+
78
+ shared_examples 'on successfull connection' do
79
+ it 'completes the returned future and returns itself' do
80
+ f = handler.connect
81
+ f.should be_complete
82
+ f.get.should equal(handler)
83
+ end
84
+
85
+ it 'is connected' do
86
+ handler.connect
87
+ handler.should be_connected
88
+ end
89
+ end
90
+
91
+ context 'when #connect_nonblock does not raise any error' do
92
+ before do
93
+ socket.stub(:connect_nonblock)
94
+ end
95
+
96
+ include_examples 'on successfull connection'
97
+ end
98
+
99
+ context 'when #connect_nonblock raises EISCONN' do
100
+ before do
101
+ socket.stub(:connect_nonblock).and_raise(Errno::EISCONN)
102
+ end
103
+
104
+ include_examples 'on successfull connection'
105
+ end
106
+
107
+ context 'when #connect_nonblock raises EALREADY' do
108
+ it 'it does nothing' do
109
+ socket.stub(:connect_nonblock).and_raise(Errno::EALREADY)
110
+ f = handler.connect
111
+ f.should_not be_complete
112
+ f.should_not be_failed
113
+ end
114
+ end
115
+
116
+ context 'when #connect_nonblock raises EINVAL' do
117
+ before do
118
+ socket_impl.stub(:sockaddr_in)
119
+ .with('PORT', 'IP2')
120
+ .and_return('SOCKADDR2')
121
+ socket_impl.stub(:new)
122
+ .with('FAMILY2', 'TYPE2', 0)
123
+ .and_return(socket)
124
+ socket.stub(:close)
125
+ end
126
+
127
+ it 'attempts to connect to the next address given by #getaddinfo' do
128
+ socket.should_receive(:connect_nonblock).with('SOCKADDR1').and_raise(Errno::EINVAL)
129
+ socket.should_receive(:connect_nonblock).with('SOCKADDR2')
130
+ handler.connect
131
+ end
132
+
133
+ it 'fails if there are no more addresses to try' do
134
+ socket.stub(:connect_nonblock).and_raise(Errno::EINVAL)
135
+ f = handler.connect
136
+ expect { f.get }.to raise_error(ConnectionError)
137
+ end
138
+ end
139
+
140
+ context 'when #connect_nonblock raises SystemCallError' do
141
+ before do
142
+ socket.stub(:connect_nonblock).and_raise(SystemCallError.new('Bork!', 9999))
143
+ socket.stub(:close)
144
+ end
145
+
146
+ it 'fails the future with a ConnectionError' do
147
+ f = handler.connect
148
+ expect { f.get }.to raise_error(ConnectionError)
149
+ end
150
+
151
+ it 'closes the socket' do
152
+ socket.should_receive(:close)
153
+ handler.connect
154
+ end
155
+
156
+ it 'calls the closed listener' do
157
+ called = false
158
+ handler.on_closed { called = true }
159
+ handler.connect
160
+ called.should be_true, 'expected the close listener to have been called'
161
+ end
162
+
163
+ it 'passes the error to the close listener' do
164
+ error = nil
165
+ handler.on_closed { |e| error = e }
166
+ handler.connect
167
+ error.should be_a(Exception)
168
+ end
169
+
170
+ it 'is closed' do
171
+ handler.connect
172
+ handler.should be_closed
173
+ end
174
+ end
175
+
176
+ context 'when Socket.getaddrinfo raises SocketError' do
177
+ before do
178
+ socket_impl.stub(:getaddrinfo).and_raise(SocketError)
179
+ end
180
+
181
+ it 'fails the returned future with a ConnectionError' do
182
+ f = handler.connect
183
+ expect { f.get }.to raise_error(ConnectionError)
184
+ end
185
+
186
+ it 'calls the close listener' do
187
+ called = false
188
+ handler.on_closed { called = true }
189
+ handler.connect
190
+ called.should be_true, 'expected the close listener to have been called'
191
+ end
192
+
193
+ it 'passes the error to the close listener' do
194
+ error = nil
195
+ handler.on_closed { |e| error = e }
196
+ handler.connect
197
+ error.should be_a(Exception)
198
+ end
199
+
200
+ it 'is closed' do
201
+ handler.connect
202
+ handler.should be_closed
203
+ end
204
+ end
205
+
206
+ context 'when it takes longer than the connection timeout to connect' do
207
+ before do
208
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
209
+ socket.stub(:close)
210
+ end
211
+
212
+ it 'fails the returned future with a ConnectionTimeoutError' do
213
+ f = handler.connect
214
+ clock.stub(:now).and_return(1)
215
+ handler.connect
216
+ socket.should_receive(:close)
217
+ clock.stub(:now).and_return(7)
218
+ handler.connect
219
+ f.should be_failed
220
+ expect { f.get }.to raise_error(ConnectionTimeoutError)
221
+ end
222
+
223
+ it 'closes the connection' do
224
+ handler.connect
225
+ clock.stub(:now).and_return(1)
226
+ handler.connect
227
+ socket.should_receive(:close)
228
+ clock.stub(:now).and_return(7)
229
+ handler.connect
230
+ end
231
+
232
+ it 'delivers a ConnectionTimeoutError to the close handler' do
233
+ error = nil
234
+ handler.on_closed { |e| error = e }
235
+ handler.connect
236
+ clock.stub(:now).and_return(7)
237
+ handler.connect
238
+ error.should be_a(ConnectionTimeoutError)
239
+ end
240
+ end
241
+ end
242
+
243
+ describe '#close' do
244
+ before do
245
+ socket.stub(:connect_nonblock)
246
+ socket.stub(:close)
247
+ handler.connect
248
+ end
249
+
250
+ it 'closes the socket' do
251
+ socket.should_receive(:close)
252
+ handler.close
253
+ end
254
+
255
+ it 'returns true' do
256
+ handler.close.should be_true
257
+ end
258
+
259
+ it 'swallows SystemCallErrors' do
260
+ socket.stub(:close).and_raise(SystemCallError.new('Bork!', 9999))
261
+ handler.close
262
+ end
263
+
264
+ it 'swallows IOErrors' do
265
+ socket.stub(:close).and_raise(IOError.new('Bork!'))
266
+ handler.close
267
+ end
268
+
269
+ it 'calls the closed listener' do
270
+ called = false
271
+ handler.on_closed { called = true }
272
+ handler.close
273
+ called.should be_true, 'expected the close listener to have been called'
274
+ end
275
+
276
+ it 'does nothing when closed a second time' do
277
+ socket.should_receive(:close).once
278
+ calls = 0
279
+ handler.on_closed { calls += 1 }
280
+ handler.close
281
+ handler.close
282
+ calls.should == 1
283
+ end
284
+
285
+ it 'returns false if it did nothing' do
286
+ handler.close
287
+ handler.close.should be_false
288
+ end
289
+
290
+ it 'is not writable when closed' do
291
+ handler.write('foo')
292
+ handler.close
293
+ handler.should_not be_writable
294
+ end
295
+ end
296
+
297
+ describe '#to_io' do
298
+ before do
299
+ socket.stub(:connect_nonblock)
300
+ socket.stub(:close)
301
+ end
302
+
303
+ it 'returns nil initially' do
304
+ handler.to_io.should be_nil
305
+ end
306
+
307
+ it 'returns the socket when connected' do
308
+ handler.connect
309
+ handler.to_io.should equal(socket)
310
+ end
311
+
312
+ it 'returns nil when closed' do
313
+ handler.connect
314
+ handler.close
315
+ handler.to_io.should be_nil
316
+ end
317
+ end
318
+
319
+ describe '#write/#flush' do
320
+ before do
321
+ socket.stub(:connect_nonblock)
322
+ handler.connect
323
+ end
324
+
325
+ it 'appends to its buffer when #write is called' do
326
+ handler.write('hello world')
327
+ end
328
+
329
+ it 'unblocks the reactor' do
330
+ unblocker.should_receive(:unblock!)
331
+ handler.write('hello world')
332
+ end
333
+
334
+ it 'is writable when there are bytes to write' do
335
+ handler.should_not be_writable
336
+ handler.write('hello world')
337
+ handler.should be_writable
338
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
339
+ handler.flush
340
+ handler.should_not be_writable
341
+ end
342
+
343
+ it 'writes to the socket from its buffer when #flush is called' do
344
+ handler.write('hello world')
345
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
346
+ handler.flush
347
+ end
348
+
349
+ it 'takes note of how much the #write_nonblock call consumed and writes the rest of the buffer on the next call to #flush' do
350
+ handler.write('hello world')
351
+ socket.should_receive(:write_nonblock).with('hello world').and_return(6)
352
+ handler.flush
353
+ socket.should_receive(:write_nonblock).with('world').and_return(5)
354
+ handler.flush
355
+ end
356
+
357
+ it 'does not call #write_nonblock if the buffer is empty' do
358
+ handler.flush
359
+ handler.write('hello world')
360
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
361
+ handler.flush
362
+ socket.should_not_receive(:write_nonblock)
363
+ handler.flush
364
+ end
365
+
366
+ context 'with a block' do
367
+ it 'yields a byte buffer to the block' do
368
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
369
+ handler.write do |buffer|
370
+ buffer << 'hello world'
371
+ end
372
+ handler.flush
373
+ end
374
+ end
375
+
376
+ context 'when #write_nonblock raises an error' do
377
+ before do
378
+ socket.stub(:close)
379
+ socket.stub(:write_nonblock).and_raise('Bork!')
380
+ end
381
+
382
+ it 'closes the socket' do
383
+ socket.should_receive(:close)
384
+ handler.write('hello world')
385
+ handler.flush
386
+ end
387
+
388
+ it 'passes the error to the close handler' do
389
+ error = nil
390
+ handler.on_closed { |e| error = e }
391
+ handler.write('hello world')
392
+ handler.flush
393
+ error.should be_a(Exception)
394
+ end
395
+ end
396
+ end
397
+
398
+ describe '#read' do
399
+ before do
400
+ socket.stub(:connect_nonblock)
401
+ handler.connect
402
+ end
403
+
404
+ it 'reads a chunk from the socket' do
405
+ socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
406
+ handler.read
407
+ end
408
+
409
+ it 'calls the data listener with the new data' do
410
+ socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
411
+ data = nil
412
+ handler.on_data { |d| data = d }
413
+ handler.read
414
+ data.should == 'foo bar'
415
+ end
416
+
417
+ context 'when #read_nonblock raises an error' do
418
+ before do
419
+ socket.stub(:close)
420
+ socket.stub(:read_nonblock).and_raise('Bork!')
421
+ end
422
+
423
+ it 'closes the socket' do
424
+ socket.should_receive(:close)
425
+ handler.read
426
+ end
427
+
428
+ it 'passes the error to the close handler' do
429
+ error = nil
430
+ handler.on_closed { |e| error = e }
431
+ handler.read
432
+ error.should be_a(Exception)
433
+ end
434
+ end
435
+ end
436
+
437
+ describe '#to_s' do
438
+ context 'returns a string that' do
439
+ it 'includes the class name' do
440
+ handler.to_s.should include('Cql::Io::Connection')
441
+ end
442
+
443
+ it 'includes the host and port' do
444
+ handler.to_s.should include('example.com:55555')
445
+ end
446
+
447
+ it 'includes the connection state' do
448
+ handler.to_s.should include('closed')
449
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
450
+ handler.connect
451
+ handler.to_s.should include('connecting')
452
+ socket.stub(:connect_nonblock)
453
+ handler.connect
454
+ handler.to_s.should include('connected')
455
+ end
456
+ end
457
+ end
458
+ end
459
+ end
460
+ end