cql-rb 1.0.6 → 1.1.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.
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