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
@@ -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