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
@@ -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 positive long' do
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 a positive int' do
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 Uuid' do
524
- frame.body.rows.first['timeuuid_column'].should == Uuid.new('a4a70900-24e1-11df-8924-001ff3591711')
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