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.
- data/README.md +4 -9
- data/lib/cql.rb +1 -0
- data/lib/cql/byte_buffer.rb +23 -7
- data/lib/cql/client.rb +11 -6
- data/lib/cql/client/asynchronous_client.rb +37 -83
- data/lib/cql/client/asynchronous_prepared_statement.rb +10 -4
- data/lib/cql/client/column_metadata.rb +16 -0
- data/lib/cql/client/request_runner.rb +46 -0
- data/lib/cql/future.rb +4 -5
- data/lib/cql/io.rb +2 -5
- data/lib/cql/io/connection.rb +220 -0
- data/lib/cql/io/io_reactor.rb +213 -185
- data/lib/cql/protocol.rb +1 -0
- data/lib/cql/protocol/cql_protocol_handler.rb +201 -0
- data/lib/cql/protocol/decoding.rb +6 -31
- data/lib/cql/protocol/encoding.rb +1 -5
- data/lib/cql/protocol/request.rb +4 -0
- data/lib/cql/protocol/responses/schema_change_result_response.rb +15 -0
- data/lib/cql/protocol/type_converter.rb +56 -76
- data/lib/cql/time_uuid.rb +104 -0
- data/lib/cql/uuid.rb +4 -2
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/asynchronous_client_spec.rb +47 -71
- data/spec/cql/client/asynchronous_prepared_statement_spec.rb +68 -0
- data/spec/cql/client/client_shared.rb +3 -3
- data/spec/cql/client/column_metadata_spec.rb +80 -0
- data/spec/cql/client/request_runner_spec.rb +120 -0
- data/spec/cql/future_spec.rb +26 -11
- data/spec/cql/io/connection_spec.rb +460 -0
- data/spec/cql/io/io_reactor_spec.rb +212 -265
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +216 -0
- data/spec/cql/protocol/decoding_spec.rb +9 -28
- data/spec/cql/protocol/encoding_spec.rb +0 -5
- data/spec/cql/protocol/request_spec.rb +16 -0
- data/spec/cql/protocol/response_frame_spec.rb +2 -2
- data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +70 -0
- data/spec/cql/time_uuid_spec.rb +136 -0
- data/spec/cql/uuid_spec.rb +1 -5
- data/spec/integration/client_spec.rb +34 -38
- data/spec/integration/io_spec.rb +283 -0
- data/spec/integration/protocol_spec.rb +53 -113
- data/spec/integration/regression_spec.rb +124 -0
- data/spec/integration/uuid_spec.rb +76 -0
- data/spec/spec_helper.rb +12 -9
- data/spec/support/fake_io_reactor.rb +52 -21
- data/spec/support/fake_server.rb +2 -2
- metadata +33 -10
- checksums.yaml +0 -15
- data/lib/cql/io/node_connection.rb +0 -209
- 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
|
17
|
-
connections.
|
16
|
+
def last_connection
|
17
|
+
connections.last
|
18
18
|
end
|
19
19
|
|
20
20
|
def requests
|
21
|
-
|
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
|
data/spec/cql/future_spec.rb
CHANGED
@@ -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
|