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
@@ -0,0 +1,216 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Cql
|
7
|
+
module Protocol
|
8
|
+
describe CqlProtocolHandler do
|
9
|
+
let :protocol_handler do
|
10
|
+
described_class.new(connection)
|
11
|
+
end
|
12
|
+
|
13
|
+
let :connection do
|
14
|
+
stub(:connection)
|
15
|
+
end
|
16
|
+
|
17
|
+
let :request do
|
18
|
+
Protocol::OptionsRequest.new
|
19
|
+
end
|
20
|
+
|
21
|
+
let :buffer do
|
22
|
+
ByteBuffer.new
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
connection.stub(:on_data) do |&h|
|
27
|
+
connection.stub(:data_listener).and_return(h)
|
28
|
+
end
|
29
|
+
connection.stub(:on_closed) do |&h|
|
30
|
+
connection.stub(:closed_listener).and_return(h)
|
31
|
+
end
|
32
|
+
connection.stub(:on_connected) do |&h|
|
33
|
+
connection.stub(:connected_listener).and_return(h)
|
34
|
+
end
|
35
|
+
protocol_handler
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#initialize' do
|
39
|
+
it 'registers as a data listener to the socket handler' do
|
40
|
+
connection.data_listener.should_not be_nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#send_request' do
|
45
|
+
before do
|
46
|
+
connection.stub(:write).and_yield(buffer)
|
47
|
+
connection.stub(:closed?).and_return(false)
|
48
|
+
|
49
|
+
connection.stub(:connected?).and_return(true)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'encodes a request frame and writes to the socket handler' do
|
53
|
+
protocol_handler.send_request(request)
|
54
|
+
buffer.to_s.should == [1, 0, 0, 5, 0].pack('C4N')
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'encodes a frame with the next available stream ID' do
|
58
|
+
protocol_handler.send_request(request)
|
59
|
+
protocol_handler.send_request(request)
|
60
|
+
buffer.to_s.should == [1, 0, 0, 5, 0].pack('C4N') + [1, 0, 1, 5, 0].pack('C4N')
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'returns a future' do
|
64
|
+
protocol_handler.send_request(request).should be_a(Future)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'completes the future when it receives a response frame with the corresponding stream ID' do
|
68
|
+
3.times { protocol_handler.send_request(request) }
|
69
|
+
future = protocol_handler.send_request(request)
|
70
|
+
connection.data_listener.call([0x81, 0, 3, 2, 0].pack('C4N'))
|
71
|
+
await(0.1) { future.complete? }
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'handles multiple response frames in the same data packet' do
|
75
|
+
futures = Array.new(4) { protocol_handler.send_request(request) }
|
76
|
+
connection.data_listener.call([0x81, 0, 2, 2, 0].pack('C4N') + [0x81, 0, 3, 2, 0].pack('C4N'))
|
77
|
+
await(0.1) { futures[2].complete? && futures[3].complete? }
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'queues the request when there are too many in flight, sending it as soon as a stream is available' do
|
81
|
+
connection.stub(:write)
|
82
|
+
futures = Array.new(130) { protocol_handler.send_request(request) }
|
83
|
+
128.times { |i| connection.data_listener.call([0x81, 0, i, 2, 0].pack('C4N')) }
|
84
|
+
futures[127].should be_complete
|
85
|
+
futures[128].should_not be_complete
|
86
|
+
2.times { |i| connection.data_listener.call([0x81, 0, i, 2, 0].pack('C4N')) }
|
87
|
+
futures[128].should be_complete
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'when the protocol handler closes' do
|
91
|
+
it 'fails all requests waiting for a reply' do
|
92
|
+
futures = Array.new(5) { protocol_handler.send_request(request) }
|
93
|
+
connection.closed_listener.call(StandardError.new('Blurgh'))
|
94
|
+
futures.should be_all(&:failed?), 'expected all requests to have failed'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'fails all queued requests' do
|
98
|
+
futures = Array.new(200) { protocol_handler.send_request(request) }
|
99
|
+
connection.closed_listener.call(StandardError.new('Blurgh'))
|
100
|
+
futures.should be_all(&:failed?), 'expected all requests to have failed'
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'fails all requests with ConnectionClosedError if there is no specific error' do
|
104
|
+
protocol_handler.send_request(request)
|
105
|
+
future = protocol_handler.send_request(request)
|
106
|
+
connection.data_listener.call([0x81, 0, 0, 2, 0].pack('C4N'))
|
107
|
+
connection.closed_listener.call(nil)
|
108
|
+
begin
|
109
|
+
future.get
|
110
|
+
rescue => e
|
111
|
+
e.should be_a(Cql::Io::ConnectionClosedError)
|
112
|
+
else
|
113
|
+
fail('No error was raised!')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'passes the error that caused the protocol handler to close to the failed requests' do
|
118
|
+
error = nil
|
119
|
+
future = protocol_handler.send_request(request)
|
120
|
+
future.on_failure { |e| error = e }
|
121
|
+
connection.closed_listener.call(StandardError.new('Blurgh'))
|
122
|
+
error.should == StandardError.new('Blurgh')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'when the protocol handler has closed' do
|
127
|
+
it 'fails all requests with NotConnectedError' do
|
128
|
+
connection.stub(:closed?).and_return(true)
|
129
|
+
f = protocol_handler.send_request(request)
|
130
|
+
expect { f.get }.to raise_error(NotConnectedError)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#close' do
|
136
|
+
it 'closes the underlying protocol handler' do
|
137
|
+
connection.should_receive(:close)
|
138
|
+
protocol_handler.close
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'returns a future which completes when the socket has closed' do
|
142
|
+
connection.stub(:close) do
|
143
|
+
connection.closed_listener.call(nil)
|
144
|
+
end
|
145
|
+
protocol_handler.close.get
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#keyspace' do
|
150
|
+
before do
|
151
|
+
connection.stub(:closed?).and_return(false)
|
152
|
+
connection.stub(:connected?).and_return(true)
|
153
|
+
connection.stub(:write)
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'is not in a keyspace initially' do
|
157
|
+
protocol_handler.keyspace.should be_nil
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'registers the keyspace it has changed to' do
|
161
|
+
f = protocol_handler.send_request(Protocol::QueryRequest.new('USE hello', :one))
|
162
|
+
connection.data_listener.call([0x81, 0, 0, 8, 4 + 2 + 5, 3, 5].pack('C4N2n') + 'hello')
|
163
|
+
f.get
|
164
|
+
protocol_handler.keyspace.should == 'hello'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
[:connected?, :closed?].each do |message|
|
169
|
+
describe "##{message}" do
|
170
|
+
it 'reflects the underlying protocol handler\'s status' do
|
171
|
+
connection.stub(message).and_return(true)
|
172
|
+
protocol_handler.send(message).should be_true
|
173
|
+
connection.stub(message).and_return(false)
|
174
|
+
protocol_handler.send(message).should be_false
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe '#on_event' do
|
180
|
+
it 'calls the callback on events' do
|
181
|
+
event = nil
|
182
|
+
protocol_handler.on_event do |e|
|
183
|
+
event = e
|
184
|
+
end
|
185
|
+
connection.data_listener.call("\x81\x00\xFF\f\x00\x00\x00+\x00\rSCHEMA_CHANGE\x00\aDROPPED\x00\x0cthe_keyspace\x00\x09the_table")
|
186
|
+
event.should == Protocol::SchemaChangeEventResponse.new('DROPPED', 'the_keyspace', 'the_table')
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'ignores errors raised by the listener' do
|
190
|
+
called = false
|
191
|
+
protocol_handler.on_event { |e| raise 'Blurgh' }
|
192
|
+
protocol_handler.on_event { |e| called = true }
|
193
|
+
connection.data_listener.call("\x81\x00\xFF\f\x00\x00\x00+\x00\rSCHEMA_CHANGE\x00\aDROPPED\x00\x0cthe_keyspace\x00\x09the_table")
|
194
|
+
called.should be_true, 'expected all event listeners to have been called'
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe '#on_closed' do
|
199
|
+
it 'calls the callback when the underlying protocol handler closes' do
|
200
|
+
called = false
|
201
|
+
protocol_handler.on_closed { called = true }
|
202
|
+
connection.closed_listener.call(StandardError.new('Blurgh'))
|
203
|
+
called.should be_true, 'expected the close listener to have been called'
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'ignores errors raised by the listener' do
|
207
|
+
called = false
|
208
|
+
protocol_handler.on_closed { |e| raise 'Blurgh' }
|
209
|
+
protocol_handler.on_closed { |e| called = true }
|
210
|
+
connection.closed_listener.call(StandardError.new('Blurgh'))
|
211
|
+
called.should be_true, 'expected all event listeners to have been called'
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -62,21 +62,6 @@ module Cql
|
|
62
62
|
Decoding.read_decimal!(buffer).should == BigDecimal.new('1042342234234.123423435647768234')
|
63
63
|
end
|
64
64
|
|
65
|
-
it 'decodes a negative decimal' do
|
66
|
-
buffer = ByteBuffer.new("\x00\x00\x00\x12\xF2\xD8\x02\xB6R\x7F\x99\xEE\x98#\x99\xA9V")
|
67
|
-
Decoding.read_decimal!(buffer).should == BigDecimal.new('-1042342234234.123423435647768234')
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'decodes a positive decimal with only fractions' do
|
71
|
-
buffer = ByteBuffer.new("\x00\x00\x00\x13*\xF8\xC4\xDF\xEB]o")
|
72
|
-
Decoding.read_decimal!(buffer).should == BigDecimal.new('0.0012095473475870063')
|
73
|
-
end
|
74
|
-
|
75
|
-
it 'decodes a negative decimal with only fractions' do
|
76
|
-
buffer = ByteBuffer.new("\x00\x00\x00\x13\xD5\a;\x20\x14\xA2\x91")
|
77
|
-
Decoding.read_decimal!(buffer).should == BigDecimal.new('-0.0012095473475870063')
|
78
|
-
end
|
79
|
-
|
80
65
|
it 'consumes the bytes' do
|
81
66
|
buffer << 'HELLO'
|
82
67
|
Decoding.read_decimal!(buffer, buffer.length - 5)
|
@@ -94,16 +79,11 @@ module Cql
|
|
94
79
|
end
|
95
80
|
|
96
81
|
describe '#read_long!' do
|
97
|
-
it 'decodes a
|
82
|
+
it 'decodes a long' do
|
98
83
|
buffer = ByteBuffer.new("\x00\x00\xca\xfe\xba\xbe\x00\x00")
|
99
84
|
Decoding.read_long!(buffer).should == 0x0000cafebabe0000
|
100
85
|
end
|
101
86
|
|
102
|
-
it 'decodes a negative long' do
|
103
|
-
buffer = ByteBuffer.new("\xff\xff\xff\xff\xff\xff\xff\xff")
|
104
|
-
Decoding.read_long!(buffer).should == -1
|
105
|
-
end
|
106
|
-
|
107
87
|
it 'consumes the bytes' do
|
108
88
|
buffer = ByteBuffer.new("\xca\xfe\xba\xbe\xca\xfe\xba\xbe\xca\xfe\xba\xbe")
|
109
89
|
Decoding.read_long!(buffer)
|
@@ -157,15 +137,10 @@ module Cql
|
|
157
137
|
ByteBuffer.new("\x00\xff\x00\xff")
|
158
138
|
end
|
159
139
|
|
160
|
-
it 'decodes
|
140
|
+
it 'decodes an int' do
|
161
141
|
Decoding.read_int!(buffer).should == 0x00ff00ff
|
162
142
|
end
|
163
143
|
|
164
|
-
it 'decodes a negative int' do
|
165
|
-
buffer = ByteBuffer.new("\xff\xff\xff\xff")
|
166
|
-
Decoding.read_int!(buffer).should == -1
|
167
|
-
end
|
168
|
-
|
169
144
|
it 'consumes the bytes' do
|
170
145
|
buffer << "\xab\xcd"
|
171
146
|
Decoding.read_int!(buffer)
|
@@ -260,10 +235,16 @@ module Cql
|
|
260
235
|
ByteBuffer.new("\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11")
|
261
236
|
end
|
262
237
|
|
263
|
-
it 'decodes a UUID' do
|
238
|
+
it 'decodes a UUID as a Cql::Uuid' do
|
264
239
|
Decoding.read_uuid!(buffer).should == Uuid.new('a4a70900-24e1-11df-8924-001ff3591711')
|
265
240
|
end
|
266
241
|
|
242
|
+
it 'decodes a UUID as a Cql::TimeUuid' do
|
243
|
+
uuid = Decoding.read_uuid!(buffer, TimeUuid)
|
244
|
+
uuid.should == TimeUuid.new('a4a70900-24e1-11df-8924-001ff3591711')
|
245
|
+
uuid.should be_a(TimeUuid)
|
246
|
+
end
|
247
|
+
|
267
248
|
it 'consumes the bytes' do
|
268
249
|
Decoding.read_uuid!(buffer)
|
269
250
|
buffer.should be_empty
|
@@ -121,11 +121,6 @@ module Cql
|
|
121
121
|
buffer.should eql_bytes("\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11")
|
122
122
|
end
|
123
123
|
|
124
|
-
it 'encodes a UUID as 16 bytes' do
|
125
|
-
Encoding.write_uuid(buffer, Uuid.new('00000000-24e1-11df-8924-001ff3591711'))
|
126
|
-
buffer.size.should eql(16)
|
127
|
-
end
|
128
|
-
|
129
124
|
it 'appends to the buffer' do
|
130
125
|
buffer << 'FOO'
|
131
126
|
Encoding.write_uuid(buffer, uuid)
|
@@ -24,6 +24,22 @@ module Cql
|
|
24
24
|
encoded_frame.should equal(buffer)
|
25
25
|
end
|
26
26
|
end
|
27
|
+
|
28
|
+
describe '.change_stream_id' do
|
29
|
+
it 'changes the stream ID byte' do
|
30
|
+
buffer = ByteBuffer.new("\x01\x00\x03\x02\x00\x00\x00\x00")
|
31
|
+
described_class.change_stream_id(99, buffer)
|
32
|
+
buffer.discard(2)
|
33
|
+
buffer.read_byte.should == 99
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'changes the stream ID byte of the frame starting at the specified offset' do
|
37
|
+
buffer = ByteBuffer.new("hello foo\x01\x00\x03\x02\x00\x00\x00\x00")
|
38
|
+
described_class.change_stream_id(99, buffer, 9)
|
39
|
+
buffer.discard(11)
|
40
|
+
buffer.read_byte.should == 99
|
41
|
+
end
|
42
|
+
end
|
27
43
|
end
|
28
44
|
end
|
29
45
|
end
|
@@ -520,8 +520,8 @@ module Cql
|
|
520
520
|
frame.body.rows.first['varint_column'].should == 1231312312331283012830129382342342412123
|
521
521
|
end
|
522
522
|
|
523
|
-
it 'decodes TIMEUUID as a
|
524
|
-
frame.body.rows.first['timeuuid_column'].should ==
|
523
|
+
it 'decodes TIMEUUID as a TimeUuid' do
|
524
|
+
frame.body.rows.first['timeuuid_column'].should == TimeUuid.new('a4a70900-24e1-11df-8924-001ff3591711')
|
525
525
|
end
|
526
526
|
|
527
527
|
it 'decodes INET as a IPAddr' do
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Cql
|
7
|
+
module Protocol
|
8
|
+
describe SchemaChangeResultResponse do
|
9
|
+
describe '#eql?' do
|
10
|
+
it 'is equal to another response with the same change, keyspace and table names' do
|
11
|
+
response1 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
12
|
+
response2 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
13
|
+
response1.should eql(response2)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'is not equal to another response with another change' do
|
17
|
+
response1 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
18
|
+
response2 = described_class.new('CREATED', 'some_keyspace', 'a_table')
|
19
|
+
response1.should_not eql(response2)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'is not equal to another response with another keyspace name' do
|
23
|
+
response1 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
24
|
+
response2 = described_class.new('DROPPED', 'another_keyspace', 'a_table')
|
25
|
+
response1.should_not eql(response2)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'is not equal to another response with another table name' do
|
29
|
+
response1 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
30
|
+
response2 = described_class.new('DROPPED', 'some_keyspace', 'another_table')
|
31
|
+
response1.should_not eql(response2)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'is aliased as ==' do
|
35
|
+
response1 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
36
|
+
response2 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
37
|
+
response3 = described_class.new('DROPPED', 'some_keyspace', 'another_table')
|
38
|
+
response1.should == response2
|
39
|
+
response2.should_not == response3
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#hash' do
|
44
|
+
it 'is the same when the change, keyspace and table names are the same' do
|
45
|
+
response1 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
46
|
+
response2 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
47
|
+
response1.hash.should == response2.hash
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'is not the same when the change is different' do
|
51
|
+
response1 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
52
|
+
response2 = described_class.new('CREATED', 'some_keyspace', 'a_table')
|
53
|
+
response1.hash.should_not == response2.hash
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'is not the same when the keyspace name is different' do
|
57
|
+
response1 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
58
|
+
response2 = described_class.new('DROPPED', 'another_keyspace', 'a_table')
|
59
|
+
response1.hash.should_not == response2.hash
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'is not the same when the table name is different' do
|
63
|
+
response1 = described_class.new('DROPPED', 'some_keyspace', 'a_table')
|
64
|
+
response2 = described_class.new('DROPPED', 'some_keyspace', 'another_table')
|
65
|
+
response1.hash.should_not == response2.hash
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Cql
|
7
|
+
describe TimeUuid do
|
8
|
+
let :time do
|
9
|
+
Time.utc(2013, 6, 7, 8, 9, 10)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#to_time' do
|
13
|
+
it 'returns a Time' do
|
14
|
+
x = TimeUuid.new('00b69180-d0e1-11e2-8b8b-0800200c9a66')
|
15
|
+
x.to_time.should be > Time.utc(2013, 6, 9, 8, 45, 57)
|
16
|
+
x.to_time.should be < Time.utc(2013, 6, 9, 8, 45, 58)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe TimeUuid::Generator do
|
22
|
+
let :generator do
|
23
|
+
described_class.new(nil, nil, stub(now: clock))
|
24
|
+
end
|
25
|
+
|
26
|
+
let :clock do
|
27
|
+
stub(:clock, to_i: 1370771820, usec: 329394)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#next' do
|
31
|
+
it 'returns a UUID generated from the current time' do
|
32
|
+
x = generator.next
|
33
|
+
x.to_time.to_i.should == 1370771820
|
34
|
+
x.to_time.usec.should == 329394
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns unique IDs even when called within a time shorter than the clock resolution' do
|
38
|
+
x1 = generator.next
|
39
|
+
x2 = generator.next
|
40
|
+
clock.stub(:usec).and_return(329394 + 1)
|
41
|
+
x3 = generator.next
|
42
|
+
x1.should_not == x2
|
43
|
+
x2.should_not == x3
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'creates a pseudo random clock ID' do
|
47
|
+
str = generator.next.to_s.split('-')[3]
|
48
|
+
str.should_not === '0000'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'uses the clock ID for all generated UUIDs' do
|
52
|
+
str1 = generator.next.to_s.split('-')[3]
|
53
|
+
str2 = generator.next.to_s.split('-')[3]
|
54
|
+
str3 = generator.next.to_s.split('-')[3]
|
55
|
+
str1.should == str2
|
56
|
+
str2.should == str3
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'creates a new clock ID when the clock has moved backwards' do
|
60
|
+
str1 = generator.next.to_s.split('-')[3]
|
61
|
+
clock.stub(:to_i).and_return(1370771820 - 5)
|
62
|
+
str2 = generator.next.to_s.split('-')[3]
|
63
|
+
str1.should_not == str2
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'creates a pseudo random node ID' do
|
67
|
+
str = generator.next.to_s.split('-')[4]
|
68
|
+
str.should_not == '000000000000'
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'uses the node ID for all generated UUIDs' do
|
72
|
+
str1 = generator.next.to_s.split('-')[4]
|
73
|
+
str2 = generator.next.to_s.split('-')[4]
|
74
|
+
str3 = generator.next.to_s.split('-')[4]
|
75
|
+
str1.should == str2
|
76
|
+
str2.should == str3
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'sets the multicast bit of the node ID (so that it does not conflict with valid MAC addresses)' do
|
80
|
+
x = generator.next.value & 0x010000000000
|
81
|
+
x.should == 0x010000000000
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'generates a version 1, variant 1 UUID' do
|
85
|
+
x = generator.from_time(clock)
|
86
|
+
(x.value & 0x10008000000000000000).should == 0x10008000000000000000
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#from_time' do
|
91
|
+
it 'returns a UUID for the specified time with a bit of random jitter' do
|
92
|
+
x = generator.from_time(clock)
|
93
|
+
x.to_time.to_i.should == 1370771820
|
94
|
+
x.to_time.usec.should be > 329394
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns a UUID for the specified time with an offset' do
|
98
|
+
x = generator.from_time(clock, 8)
|
99
|
+
x.to_time.to_i.should == 1370771820
|
100
|
+
x.to_time.usec.should == 329394 + 8
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when specifying a custom clock ID' do
|
105
|
+
it 'uses the lower 14 bits of the specified clock ID' do
|
106
|
+
g = described_class.new(nil, 0x2bad, stub(now: clock))
|
107
|
+
(g.next.value >> 48 & 0x3fff).should == 0x2bad
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'ensures that the high bit of the clock ID is 1 (the variant)' do
|
111
|
+
g = described_class.new(nil, 0x2bad, stub(now: clock))
|
112
|
+
(g.next.value >> 60 & 0b1000).should == 0b1000
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'generates a new random clock ID if time has moved backwards' do
|
116
|
+
g = described_class.new(nil, 0x2bad, stub(now: clock))
|
117
|
+
str1 = g.next.to_s.split('-')[3]
|
118
|
+
clock.stub(:to_i).and_return(1370771820 - 2)
|
119
|
+
str2 = g.next.to_s.split('-')[3]
|
120
|
+
str1.should_not == str2
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when specifying a custom node ID' do
|
125
|
+
it 'uses the lower 48 bits of the specified node ID' do
|
126
|
+
g = described_class.new(0xd00b1ed00b1ed00b, 0x0000, stub(now: clock))
|
127
|
+
g.next.to_s.should end_with('00-1ed00b1ed00b')
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'does not modify the multicast bit' do
|
131
|
+
g = described_class.new(0x000000000000, 0x0000, stub(now: clock))
|
132
|
+
g.next.to_s.should end_with('00-000000000000')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|