winescout-redis-rb 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,131 @@
1
+ ##
2
+ # This class represents a redis server instance.
3
+
4
+ class Server
5
+
6
+ ##
7
+ # The amount of time to wait before attempting to re-establish a
8
+ # connection with a server that is marked dead.
9
+
10
+ RETRY_DELAY = 30.0
11
+
12
+ ##
13
+ # The host the redis server is running on.
14
+
15
+ attr_reader :host
16
+
17
+ ##
18
+ # The port the redis server is listening on.
19
+
20
+ attr_reader :port
21
+
22
+ ##
23
+ #
24
+
25
+ attr_reader :replica
26
+
27
+ ##
28
+ # The time of next retry if the connection is dead.
29
+
30
+ attr_reader :retry
31
+
32
+ ##
33
+ # A text status string describing the state of the server.
34
+
35
+ attr_reader :status
36
+
37
+ ##
38
+ # Create a new Redis::Server object for the redis instance
39
+ # listening on the given host and port.
40
+
41
+ def initialize(host, port = DEFAULT_PORT)
42
+ raise ArgumentError, "No host specified" if host.nil? or host.empty?
43
+ raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
44
+
45
+ @host = host
46
+ @port = port.to_i
47
+
48
+ @sock = nil
49
+ @retry = nil
50
+ @status = 'NOT CONNECTED'
51
+ @timeout = 1
52
+ end
53
+
54
+ ##
55
+ # Return a string representation of the server object.
56
+ def inspect
57
+ "<Redis::Server: %s:%d (%s)>" % [@host, @port, @status]
58
+ end
59
+
60
+ ##
61
+ # Try to connect to the redis server targeted by this object.
62
+ # Returns the connected socket object on success or nil on failure.
63
+
64
+ def socket
65
+ return @sock if @sock and not @sock.closed?
66
+
67
+ @sock = nil
68
+
69
+ # If the host was dead, don't retry for a while.
70
+ return if @retry and @retry > Time.now
71
+
72
+ # Attempt to connect if not already connected.
73
+ begin
74
+ @sock = connect_to(@host, @port, @timeout)
75
+ @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
76
+ @retry = nil
77
+ @status = 'CONNECTED'
78
+ rescue Errno::EPIPE, Errno::ECONNREFUSED => e
79
+ puts "Socket died... socket: #{@sock.inspect}\n" if $debug
80
+ @sock.close
81
+ retry
82
+ rescue SocketError, SystemCallError, IOError => err
83
+ puts "Unable to open socket: #{err.class.name}, #{err.message}" if $debug
84
+ mark_dead err
85
+ end
86
+
87
+ return @sock
88
+ end
89
+
90
+ def connect_to(host, port, timeout=nil)
91
+ addrs = Socket.getaddrinfo(host, nil)
92
+ addr = addrs.detect { |ad| ad[0] == 'AF_INET' }
93
+ sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
94
+ #addr = Socket.getaddrinfo(host, nil)
95
+ #sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
96
+
97
+ if timeout
98
+ secs = Integer(timeout)
99
+ usecs = Integer((timeout - secs) * 1_000_000)
100
+ optval = [secs, usecs].pack("l_2")
101
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
102
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
103
+ end
104
+ sock.connect(Socket.pack_sockaddr_in(port, addr[3]))
105
+ sock
106
+ end
107
+
108
+ ##
109
+ # Close the connection to the redis server targeted by this
110
+ # object. The server is not considered dead.
111
+
112
+ def close
113
+ @sock.close if @sock && !@sock.closed?
114
+ @sock = nil
115
+ @retry = nil
116
+ @status = "NOT CONNECTED"
117
+ end
118
+
119
+ ##
120
+ # Mark the server as dead and close its socket.
121
+ def mark_dead(error)
122
+ @sock.close if @sock && !@sock.closed?
123
+ @sock = nil
124
+ @retry = Time.now #+ RETRY_DELAY
125
+
126
+ reason = "#{error.class.name}: #{error.message}"
127
+ @status = sprintf "%s:%s DEAD (%s), will retry at %s", @host, @port, reason, @retry
128
+ puts @status
129
+ end
130
+
131
+ end
@@ -0,0 +1,323 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class Foo
4
+ attr_accessor :bar
5
+ def initialize(bar)
6
+ @bar = bar
7
+ end
8
+
9
+ def ==(other)
10
+ @bar == other.bar
11
+ end
12
+ end
13
+
14
+ describe "redis" do
15
+ before(:all) do
16
+ @r = Redis.new
17
+ @r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data
18
+ end
19
+
20
+ before(:each) do
21
+ @r['foo'] = 'bar'
22
+ end
23
+
24
+ after(:each) do
25
+ @r.flush_db
26
+ end
27
+
28
+ after(:all) do
29
+ @r.quit
30
+ end
31
+
32
+
33
+ it "should be able to GET a key" do
34
+ @r['foo'].should == 'bar'
35
+ end
36
+
37
+ it "should be able to SET a key" do
38
+ @r['foo'] = 'nik'
39
+ @r['foo'].should == 'nik'
40
+ end
41
+
42
+ it "should be able to SETNX(set_unless_exists)" do
43
+ @r['foo'] = 'nik'
44
+ @r['foo'].should == 'nik'
45
+ @r.set_unless_exists 'foo', 'bar'
46
+ @r['foo'].should == 'nik'
47
+ end
48
+ #
49
+ it "should be able to INCR(increment) a key" do
50
+ @r.delete('counter')
51
+ @r.incr('counter').should == 1
52
+ @r.incr('counter').should == 2
53
+ @r.incr('counter').should == 3
54
+ end
55
+ #
56
+ it "should be able to DECR(decrement) a key" do
57
+ @r.delete('counter')
58
+ @r.incr('counter').should == 1
59
+ @r.incr('counter').should == 2
60
+ @r.incr('counter').should == 3
61
+ @r.decr('counter').should == 2
62
+ @r.decr('counter').should == 1
63
+ @r.decr('counter').should == 0
64
+ end
65
+ #
66
+ it "should be able to RANDKEY(return a random key)" do
67
+ @r.randkey.should_not be_nil
68
+ end
69
+ #
70
+ it "should be able to RENAME a key" do
71
+ @r.delete 'foo'
72
+ @r.delete 'bar'
73
+ @r['foo'] = 'hi'
74
+ @r.rename! 'foo', 'bar'
75
+ @r['bar'].should == 'hi'
76
+ end
77
+ #
78
+ it "should be able to RENAMENX(rename unless the new key already exists) a key" do
79
+ @r.delete 'foo'
80
+ @r.delete 'bar'
81
+ @r['foo'] = 'hi'
82
+ @r['bar'] = 'ohai'
83
+ lambda {@r.rename 'foo', 'bar'}.should raise_error(RedisRenameError)
84
+ @r['bar'].should == 'ohai'
85
+ end
86
+ #
87
+ it "should be able to EXISTS(check if key exists)" do
88
+ @r['foo'] = 'nik'
89
+ @r.key?('foo').should be_true
90
+ @r.delete 'foo'
91
+ @r.key?('foo').should be_false
92
+ end
93
+ #
94
+ it "should be able to KEYS(glob for keys)" do
95
+ @r.keys("f*").each do |key|
96
+ @r.delete key
97
+ end
98
+ @r['f'] = 'nik'
99
+ @r['fo'] = 'nak'
100
+ @r['foo'] = 'qux'
101
+ @r.keys("f*").sort.should == ['f','fo', 'foo'].sort
102
+ end
103
+ #
104
+ it "should be able to check the TYPE of a key" do
105
+ @r['foo'] = 'nik'
106
+ @r.type?('foo').should == "string"
107
+ @r.delete 'foo'
108
+ @r.type?('foo').should == "none"
109
+ end
110
+ #
111
+ it "should be able to push to the head of a list" do
112
+ @r.push_head "list", 'hello'
113
+ @r.push_head "list", 42
114
+ @r.type?('list').should == "list"
115
+ @r.list_length('list').should == 2
116
+ @r.pop_head('list').should == '42'
117
+ @r.delete('list')
118
+ end
119
+ #
120
+ it "should be able to push to the tail of a list" do
121
+ @r.push_tail "list", 'hello'
122
+ @r.type?('list').should == "list"
123
+ @r.list_length('list').should == 1
124
+ @r.delete('list')
125
+ end
126
+ #
127
+ it "should be able to pop the tail of a list" do
128
+ @r.push_tail "list", 'hello'
129
+ @r.push_tail "list", 'goodbye'
130
+ @r.type?('list').should == "list"
131
+ @r.list_length('list').should == 2
132
+ @r.pop_tail('list').should == 'goodbye'
133
+ @r.delete('list')
134
+ end
135
+ #
136
+ it "should be able to pop the head of a list" do
137
+ @r.push_tail "list", 'hello'
138
+ @r.push_tail "list", 'goodbye'
139
+ @r.type?('list').should == "list"
140
+ @r.list_length('list').should == 2
141
+ @r.pop_head('list').should == 'hello'
142
+ @r.delete('list')
143
+ end
144
+ #
145
+ it "should be able to get the length of a list" do
146
+ @r.push_tail "list", 'hello'
147
+ @r.push_tail "list", 'goodbye'
148
+ @r.type?('list').should == "list"
149
+ @r.list_length('list').should == 2
150
+ @r.delete('list')
151
+ end
152
+ #
153
+ it "should be able to get a range of values from a list" do
154
+ @r.push_tail "list", 'hello'
155
+ @r.push_tail "list", 'goodbye'
156
+ @r.push_tail "list", '1'
157
+ @r.push_tail "list", '2'
158
+ @r.push_tail "list", '3'
159
+ @r.type?('list').should == "list"
160
+ @r.list_length('list').should == 5
161
+ @r.list_range('list', 2, -1).should == ['1', '2', '3']
162
+ @r.delete('list')
163
+ end
164
+ #
165
+ it "should be able to trim a list" do
166
+ @r.push_tail "list", 'hello'
167
+ @r.push_tail "list", 'goodbye'
168
+ @r.push_tail "list", '1'
169
+ @r.push_tail "list", '2'
170
+ @r.push_tail "list", '3'
171
+ @r.type?('list').should == "list"
172
+ @r.list_length('list').should == 5
173
+ @r.list_trim 'list', 0, 1
174
+ @r.list_length('list').should == 2
175
+ @r.list_range('list', 0, -1).should == ['hello', 'goodbye']
176
+ @r.delete('list')
177
+ end
178
+ #
179
+ it "should be able to get a value by indexing into a list" do
180
+ @r.push_tail "list", 'hello'
181
+ @r.push_tail "list", 'goodbye'
182
+ @r.type?('list').should == "list"
183
+ @r.list_length('list').should == 2
184
+ @r.list_index('list', 1).should == 'goodbye'
185
+ @r.delete('list')
186
+ end
187
+ #
188
+ it "should be able to set a value by indexing into a list" do
189
+ @r.push_tail "list", 'hello'
190
+ @r.push_tail "list", 'hello'
191
+ @r.type?('list').should == "list"
192
+ @r.list_length('list').should == 2
193
+ @r.list_set('list', 1, 'goodbye').should be_true
194
+ @r.list_index('list', 1).should == 'goodbye'
195
+ @r.delete('list')
196
+ end
197
+ #
198
+ it "should be able to remove values from a list LREM" do
199
+ @r.push_tail "list", 'hello'
200
+ @r.push_tail "list", 'goodbye'
201
+ @r.type?('list').should == "list"
202
+ @r.list_length('list').should == 2
203
+ @r.list_rm('list', 1, 'hello').should == 1
204
+ @r.list_range('list', 0, -1).should == ['goodbye']
205
+ @r.delete('list')
206
+ end
207
+ #
208
+ it "should be able add members to a set" do
209
+ @r.set_add "set", 'key1'
210
+ @r.set_add "set", 'key2'
211
+ @r.type?('set').should == "set"
212
+ @r.set_count('set').should == 2
213
+ @r.set_members('set').sort.should == ['key1', 'key2'].sort
214
+ @r.delete('set')
215
+ end
216
+ #
217
+ it "should be able delete members to a set" do
218
+ @r.set_add "set", 'key1'
219
+ @r.set_add "set", 'key2'
220
+ @r.type?('set').should == "set"
221
+ @r.set_count('set').should == 2
222
+ @r.set_members('set').should == Set.new(['key1', 'key2'])
223
+ @r.set_delete('set', 'key1')
224
+ @r.set_count('set').should == 1
225
+ @r.set_members('set').should == Set.new(['key2'])
226
+ @r.delete('set')
227
+ end
228
+ #
229
+ it "should be able count the members of a set" do
230
+ @r.set_add "set", 'key1'
231
+ @r.set_add "set", 'key2'
232
+ @r.type?('set').should == "set"
233
+ @r.set_count('set').should == 2
234
+ @r.delete('set')
235
+ end
236
+ #
237
+ it "should be able test for set membership" do
238
+ @r.set_add "set", 'key1'
239
+ @r.set_add "set", 'key2'
240
+ @r.type?('set').should == "set"
241
+ @r.set_count('set').should == 2
242
+ @r.set_member?('set', 'key1').should be_true
243
+ @r.set_member?('set', 'key2').should be_true
244
+ @r.set_member?('set', 'notthere').should be_false
245
+ @r.delete('set')
246
+ end
247
+ #
248
+ it "should be able to do set intersection" do
249
+ @r.set_add "set", 'key1'
250
+ @r.set_add "set", 'key2'
251
+ @r.set_add "set2", 'key2'
252
+ @r.set_intersect('set', 'set2').should == Set.new(['key2'])
253
+ @r.delete('set')
254
+ end
255
+ #
256
+ it "should be able to do set intersection and store the results in a key" do
257
+ @r.set_add "set", 'key1'
258
+ @r.set_add "set", 'key2'
259
+ @r.set_add "set2", 'key2'
260
+ @r.set_inter_store('newone', 'set', 'set2')
261
+ @r.set_members('newone').should == Set.new(['key2'])
262
+ @r.delete('set')
263
+ end
264
+ #
265
+ it "should be able to do crazy SORT queries" do
266
+ @r['dog_1'] = 'louie'
267
+ @r.push_tail 'dogs', 1
268
+ @r['dog_2'] = 'lucy'
269
+ @r.push_tail 'dogs', 2
270
+ @r['dog_3'] = 'max'
271
+ @r.push_tail 'dogs', 3
272
+ @r['dog_4'] = 'taj'
273
+ @r.push_tail 'dogs', 4
274
+ @r.sort('dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie']
275
+ @r.sort('dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj']
276
+ end
277
+ #
278
+ it "should provide info" do
279
+ [:last_save_time, :redis_version, :total_connections_received, :connected_clients, :total_commands_processed, :connected_slaves, :uptime_in_seconds, :used_memory, :uptime_in_days, :changes_since_last_save].each do |x|
280
+ @r.info.keys.should include(x)
281
+ end
282
+ end
283
+ #
284
+ it "should be able to flush the database" do
285
+ @r['key1'] = 'keyone'
286
+ @r['key2'] = 'keytwo'
287
+ @r.keys('*').sort.should == ['foo', 'key1', 'key2'] #foo from before
288
+ @r.flush_db
289
+ @r.keys('*').should == []
290
+ end
291
+ #
292
+ it "should be able to provide the last save time" do
293
+ savetime = @r.last_save
294
+ Time.at(savetime).class.should == Time
295
+ Time.at(savetime).should <= Time.now
296
+ end
297
+
298
+ it "should be able to MGET keys" do
299
+ @r['foo'] = 1000
300
+ @r['bar'] = 2000
301
+ @r.mget('foo', 'bar').should == ['1000', '2000']
302
+ @r.mget('foo', 'bar', 'baz').should == ['1000', '2000', nil]
303
+ end
304
+
305
+ it "should bgsave" do
306
+ lambda {@r.bgsave}.should_not raise_error(RedisError)
307
+ end
308
+
309
+ it "should handle multiple servers" do
310
+ require 'dist_redis'
311
+ @r = DistRedis.new('localhost:6379', '127.0.0.1:6379')
312
+ @r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data
313
+
314
+ 100.times do |idx|
315
+ @r[idx] = "foo#{idx}"
316
+ end
317
+
318
+ 100.times do |idx|
319
+ @r[idx].should == "foo#{idx}"
320
+ end
321
+ end
322
+
323
+ end