mikeg-vanity 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG +153 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.rdoc +83 -0
  4. data/bin/vanity +53 -0
  5. data/lib/vanity.rb +38 -0
  6. data/lib/vanity/backport.rb +43 -0
  7. data/lib/vanity/commands.rb +2 -0
  8. data/lib/vanity/commands/list.rb +21 -0
  9. data/lib/vanity/commands/report.rb +60 -0
  10. data/lib/vanity/experiment/ab_test.rb +477 -0
  11. data/lib/vanity/experiment/base.rb +212 -0
  12. data/lib/vanity/helpers.rb +59 -0
  13. data/lib/vanity/metric/active_record.rb +77 -0
  14. data/lib/vanity/metric/base.rb +221 -0
  15. data/lib/vanity/metric/google_analytics.rb +70 -0
  16. data/lib/vanity/mock_redis.rb +76 -0
  17. data/lib/vanity/playground.rb +197 -0
  18. data/lib/vanity/rails.rb +22 -0
  19. data/lib/vanity/rails/dashboard.rb +24 -0
  20. data/lib/vanity/rails/helpers.rb +158 -0
  21. data/lib/vanity/rails/testing.rb +11 -0
  22. data/lib/vanity/templates/_ab_test.erb +26 -0
  23. data/lib/vanity/templates/_experiment.erb +5 -0
  24. data/lib/vanity/templates/_experiments.erb +7 -0
  25. data/lib/vanity/templates/_metric.erb +14 -0
  26. data/lib/vanity/templates/_metrics.erb +13 -0
  27. data/lib/vanity/templates/_report.erb +27 -0
  28. data/lib/vanity/templates/flot.min.js +1 -0
  29. data/lib/vanity/templates/jquery.min.js +19 -0
  30. data/lib/vanity/templates/vanity.css +26 -0
  31. data/lib/vanity/templates/vanity.js +82 -0
  32. data/test/ab_test_test.rb +656 -0
  33. data/test/experiment_test.rb +136 -0
  34. data/test/experiments/age_and_zipcode.rb +19 -0
  35. data/test/experiments/metrics/cheers.rb +3 -0
  36. data/test/experiments/metrics/signups.rb +2 -0
  37. data/test/experiments/metrics/yawns.rb +3 -0
  38. data/test/experiments/null_abc.rb +5 -0
  39. data/test/metric_test.rb +518 -0
  40. data/test/playground_test.rb +10 -0
  41. data/test/rails_test.rb +104 -0
  42. data/test/test_helper.rb +135 -0
  43. data/vanity.gemspec +18 -0
  44. data/vendor/redis-rb/LICENSE +20 -0
  45. data/vendor/redis-rb/README.markdown +36 -0
  46. data/vendor/redis-rb/Rakefile +62 -0
  47. data/vendor/redis-rb/bench.rb +44 -0
  48. data/vendor/redis-rb/benchmarking/suite.rb +24 -0
  49. data/vendor/redis-rb/benchmarking/worker.rb +71 -0
  50. data/vendor/redis-rb/bin/distredis +33 -0
  51. data/vendor/redis-rb/examples/basic.rb +16 -0
  52. data/vendor/redis-rb/examples/incr-decr.rb +18 -0
  53. data/vendor/redis-rb/examples/list.rb +26 -0
  54. data/vendor/redis-rb/examples/sets.rb +36 -0
  55. data/vendor/redis-rb/lib/dist_redis.rb +124 -0
  56. data/vendor/redis-rb/lib/hash_ring.rb +128 -0
  57. data/vendor/redis-rb/lib/pipeline.rb +21 -0
  58. data/vendor/redis-rb/lib/redis.rb +370 -0
  59. data/vendor/redis-rb/lib/redis/raketasks.rb +1 -0
  60. data/vendor/redis-rb/profile.rb +22 -0
  61. data/vendor/redis-rb/redis-rb.gemspec +30 -0
  62. data/vendor/redis-rb/spec/redis_spec.rb +637 -0
  63. data/vendor/redis-rb/spec/spec_helper.rb +4 -0
  64. data/vendor/redis-rb/speed.rb +16 -0
  65. data/vendor/redis-rb/tasks/redis.tasks.rb +140 -0
  66. metadata +125 -0
@@ -0,0 +1,21 @@
1
+ class Redis
2
+ class Pipeline < Redis
3
+ BUFFER_SIZE = 50_000
4
+
5
+ def initialize(redis)
6
+ @redis = redis
7
+ @commands = []
8
+ end
9
+
10
+ def call_command(command)
11
+ @commands << command
12
+ end
13
+
14
+ def execute
15
+ return if @commands.empty?
16
+ @redis.call_command(@commands)
17
+ @commands.clear
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,370 @@
1
+ require 'socket'
2
+ require File.join(File.dirname(__FILE__),'pipeline')
3
+
4
+ begin
5
+ if RUBY_VERSION >= '1.9'
6
+ require 'timeout'
7
+ RedisTimer = Timeout
8
+ else
9
+ require 'system_timer'
10
+ RedisTimer = SystemTimer
11
+ end
12
+ rescue LoadError
13
+ RedisTimer = nil
14
+ end
15
+
16
+ class Redis
17
+ OK = "OK".freeze
18
+ MINUS = "-".freeze
19
+ PLUS = "+".freeze
20
+ COLON = ":".freeze
21
+ DOLLAR = "$".freeze
22
+ ASTERISK = "*".freeze
23
+
24
+ BULK_COMMANDS = {
25
+ "set" => true,
26
+ "setnx" => true,
27
+ "rpush" => true,
28
+ "lpush" => true,
29
+ "lset" => true,
30
+ "lrem" => true,
31
+ "sadd" => true,
32
+ "srem" => true,
33
+ "sismember" => true,
34
+ "rpoplpush" => true,
35
+ "echo" => true,
36
+ "getset" => true,
37
+ "smove" => true,
38
+ "zadd" => true,
39
+ "zrem" => true,
40
+ "zscore" => true
41
+ }
42
+
43
+ MULTI_BULK_COMMANDS = {
44
+ "mset" => true,
45
+ "msetnx" => true
46
+ }
47
+
48
+ BOOLEAN_PROCESSOR = lambda{|r| r == 1 }
49
+
50
+ REPLY_PROCESSOR = {
51
+ "exists" => BOOLEAN_PROCESSOR,
52
+ "sismember" => BOOLEAN_PROCESSOR,
53
+ "sadd" => BOOLEAN_PROCESSOR,
54
+ "srem" => BOOLEAN_PROCESSOR,
55
+ "smove" => BOOLEAN_PROCESSOR,
56
+ "zadd" => BOOLEAN_PROCESSOR,
57
+ "zrem" => BOOLEAN_PROCESSOR,
58
+ "move" => BOOLEAN_PROCESSOR,
59
+ "setnx" => BOOLEAN_PROCESSOR,
60
+ "del" => BOOLEAN_PROCESSOR,
61
+ "renamenx" => BOOLEAN_PROCESSOR,
62
+ "expire" => BOOLEAN_PROCESSOR,
63
+ "keys" => lambda{|r| r.split(" ")},
64
+ "info" => lambda{|r|
65
+ info = {}
66
+ r.each_line {|kv|
67
+ k,v = kv.split(":",2).map{|x| x.chomp}
68
+ info[k.to_sym] = v
69
+ }
70
+ info
71
+ }
72
+ }
73
+
74
+ ALIASES = {
75
+ "flush_db" => "flushdb",
76
+ "flush_all" => "flushall",
77
+ "last_save" => "lastsave",
78
+ "key?" => "exists",
79
+ "delete" => "del",
80
+ "randkey" => "randomkey",
81
+ "list_length" => "llen",
82
+ "push_tail" => "rpush",
83
+ "push_head" => "lpush",
84
+ "pop_tail" => "rpop",
85
+ "pop_head" => "lpop",
86
+ "list_set" => "lset",
87
+ "list_range" => "lrange",
88
+ "list_trim" => "ltrim",
89
+ "list_index" => "lindex",
90
+ "list_rm" => "lrem",
91
+ "set_add" => "sadd",
92
+ "set_delete" => "srem",
93
+ "set_count" => "scard",
94
+ "set_member?" => "sismember",
95
+ "set_members" => "smembers",
96
+ "set_intersect" => "sinter",
97
+ "set_intersect_store" => "sinterstore",
98
+ "set_inter_store" => "sinterstore",
99
+ "set_union" => "sunion",
100
+ "set_union_store" => "sunionstore",
101
+ "set_diff" => "sdiff",
102
+ "set_diff_store" => "sdiffstore",
103
+ "set_move" => "smove",
104
+ "set_unless_exists" => "setnx",
105
+ "rename_unless_exists" => "renamenx",
106
+ "type?" => "type",
107
+ "zset_add" => "zadd",
108
+ "zset_count" => 'zcard',
109
+ "zset_range_by_score" => 'zrangebyscore',
110
+ "zset_reverse_range" => 'zrevrange',
111
+ "zset_range" => 'zrange',
112
+ "zset_delete" => 'zrem',
113
+ "zset_score" => 'zscore'
114
+ }
115
+
116
+ DISABLED_COMMANDS = {
117
+ "monitor" => true,
118
+ "sync" => true
119
+ }
120
+
121
+ def initialize(options = {})
122
+ @host = options[:host] || '127.0.0.1'
123
+ @port = (options[:port] || 6379).to_i
124
+ @db = (options[:db] || 0).to_i
125
+ @timeout = (options[:timeout] || 5).to_i
126
+ @password = options[:password]
127
+ @logger = options[:logger]
128
+ @thread_safe = options[:thread_safe]
129
+ @mutex = Mutex.new if @thread_safe
130
+
131
+ @logger.info { self.to_s } if @logger
132
+ end
133
+
134
+ def to_s
135
+ "Redis Client connected to #{server} against DB #{@db}"
136
+ end
137
+
138
+ def server
139
+ "#{@host}:#{@port}"
140
+ end
141
+
142
+ def connect_to_server
143
+ @sock = connect_to(@host, @port, @timeout == 0 ? nil : @timeout)
144
+ call_command(["auth",@password]) if @password
145
+ call_command(["select",@db]) unless @db == 0
146
+ end
147
+
148
+ def connect_to(host, port, timeout=nil)
149
+ # We support connect() timeout only if system_timer is availabe
150
+ # or if we are running against Ruby >= 1.9
151
+ # Timeout reading from the socket instead will be supported anyway.
152
+ if @timeout != 0 and RedisTimer
153
+ begin
154
+ sock = TCPSocket.new(host, port)
155
+ rescue Timeout::Error
156
+ @sock = nil
157
+ raise Timeout::Error, "Timeout connecting to the server"
158
+ end
159
+ else
160
+ sock = TCPSocket.new(host, port)
161
+ end
162
+ sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
163
+
164
+ # If the timeout is set we set the low level socket options in order
165
+ # to make sure a blocking read will return after the specified number
166
+ # of seconds. This hack is from memcached ruby client.
167
+ if timeout
168
+ secs = Integer(timeout)
169
+ usecs = Integer((timeout - secs) * 1_000_000)
170
+ optval = [secs, usecs].pack("l_2")
171
+ begin
172
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
173
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
174
+ rescue Exception => ex
175
+ # Solaris, for one, does not like/support socket timeouts.
176
+ @logger.info "Unable to use raw socket timeouts: #{ex.class.name}: #{ex.message}" if @logger
177
+ end
178
+ end
179
+ sock
180
+ end
181
+
182
+ def method_missing(*argv)
183
+ call_command(argv)
184
+ end
185
+
186
+ def call_command(argv)
187
+ @logger.debug { argv.inspect } if @logger
188
+
189
+ # this wrapper to raw_call_command handle reconnection on socket
190
+ # error. We try to reconnect just one time, otherwise let the error
191
+ # araise.
192
+ connect_to_server if !@sock
193
+
194
+ begin
195
+ raw_call_command(argv.dup)
196
+ rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED
197
+ @sock.close
198
+ @sock = nil
199
+ connect_to_server
200
+ raw_call_command(argv.dup)
201
+ end
202
+ end
203
+
204
+ def raw_call_command(argvp)
205
+ pipeline = argvp[0].is_a?(Array)
206
+
207
+ unless pipeline
208
+ argvv = [argvp]
209
+ else
210
+ argvv = argvp
211
+ end
212
+
213
+ if MULTI_BULK_COMMANDS[argvv.flatten[0].to_s]
214
+ # TODO improve this code
215
+ argvp = argvv.flatten
216
+ values = argvp.pop.to_a.flatten
217
+ argvp = values.unshift(argvp[0])
218
+ command = ["*#{argvp.size}"]
219
+ argvp.each do |v|
220
+ v = v.to_s
221
+ command << "$#{get_size(v)}"
222
+ command << v
223
+ end
224
+ command = command.map {|cmd| "#{cmd}\r\n"}.join
225
+ else
226
+ command = ""
227
+ argvv.each do |argv|
228
+ bulk = nil
229
+ argv[0] = argv[0].to_s.downcase
230
+ argv[0] = ALIASES[argv[0]] if ALIASES[argv[0]]
231
+ raise "#{argv[0]} command is disabled" if DISABLED_COMMANDS[argv[0]]
232
+ if BULK_COMMANDS[argv[0]] and argv.length > 1
233
+ bulk = argv[-1].to_s
234
+ argv[-1] = get_size(bulk)
235
+ end
236
+ command << "#{argv.join(' ')}\r\n"
237
+ command << "#{bulk}\r\n" if bulk
238
+ end
239
+ end
240
+ results = maybe_lock { process_command(command, argvv) }
241
+
242
+ return pipeline ? results : results[0]
243
+ end
244
+
245
+ def process_command(command, argvv)
246
+ @sock.write(command)
247
+ argvv.map do |argv|
248
+ processor = REPLY_PROCESSOR[argv[0]]
249
+ processor ? processor.call(read_reply) : read_reply
250
+ end
251
+ end
252
+
253
+ def maybe_lock(&block)
254
+ if @thread_safe
255
+ @mutex.synchronize &block
256
+ else
257
+ block.call
258
+ end
259
+ end
260
+
261
+ def select(*args)
262
+ raise "SELECT not allowed, use the :db option when creating the object"
263
+ end
264
+
265
+ def [](key)
266
+ self.get(key)
267
+ end
268
+
269
+ def []=(key,value)
270
+ set(key,value)
271
+ end
272
+
273
+ def set(key, value, expiry=nil)
274
+ s = call_command([:set, key, value]) == OK
275
+ expire(key, expiry) if s && expiry
276
+ s
277
+ end
278
+
279
+ def sort(key, options = {})
280
+ cmd = ["SORT"]
281
+ cmd << key
282
+ cmd << "BY #{options[:by]}" if options[:by]
283
+ cmd << "GET #{[options[:get]].flatten * ' GET '}" if options[:get]
284
+ cmd << "#{options[:order]}" if options[:order]
285
+ cmd << "LIMIT #{options[:limit].join(' ')}" if options[:limit]
286
+ call_command(cmd)
287
+ end
288
+
289
+ def incr(key, increment = nil)
290
+ call_command(increment ? ["incrby",key,increment] : ["incr",key])
291
+ end
292
+
293
+ def decr(key,decrement = nil)
294
+ call_command(decrement ? ["decrby",key,decrement] : ["decr",key])
295
+ end
296
+
297
+ # Similar to memcache.rb's #get_multi, returns a hash mapping
298
+ # keys to values.
299
+ def mapped_mget(*keys)
300
+ result = {}
301
+ mget(*keys).each do |value|
302
+ key = keys.shift
303
+ result.merge!(key => value) unless value.nil?
304
+ end
305
+ result
306
+ end
307
+
308
+ # Ruby defines a now deprecated type method so we need to override it here
309
+ # since it will never hit method_missing
310
+ def type(key)
311
+ call_command(['type', key])
312
+ end
313
+
314
+ def quit
315
+ call_command(['quit'])
316
+ rescue Errno::ECONNRESET
317
+ end
318
+
319
+ def pipelined(&block)
320
+ pipeline = Pipeline.new self
321
+ yield pipeline
322
+ pipeline.execute
323
+ end
324
+
325
+ def read_reply
326
+ # We read the first byte using read() mainly because gets() is
327
+ # immune to raw socket timeouts.
328
+ begin
329
+ rtype = @sock.read(1)
330
+ rescue Errno::EAGAIN
331
+ # We want to make sure it reconnects on the next command after the
332
+ # timeout. Otherwise the server may reply in the meantime leaving
333
+ # the protocol in a desync status.
334
+ @sock = nil
335
+ raise Errno::EAGAIN, "Timeout reading from the socket"
336
+ end
337
+
338
+ raise Errno::ECONNRESET,"Connection lost" if !rtype
339
+ line = @sock.gets
340
+ case rtype
341
+ when MINUS
342
+ raise MINUS + line.strip
343
+ when PLUS
344
+ line.strip
345
+ when COLON
346
+ line.to_i
347
+ when DOLLAR
348
+ bulklen = line.to_i
349
+ return nil if bulklen == -1
350
+ data = @sock.read(bulklen)
351
+ @sock.read(2) # CRLF
352
+ data
353
+ when ASTERISK
354
+ objects = line.to_i
355
+ return nil if bulklen == -1
356
+ res = []
357
+ objects.times {
358
+ res << read_reply
359
+ }
360
+ res
361
+ else
362
+ raise "Protocol error, got '#{rtype}' as initial reply byte"
363
+ end
364
+ end
365
+
366
+ private
367
+ def get_size(string)
368
+ string.respond_to?(:bytesize) ? string.bytesize : string.size
369
+ end
370
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/../../tasks/redis.tasks"
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'ruby-prof'
3
+ require "#{File.dirname(__FILE__)}/lib/redis"
4
+
5
+
6
+ mode = ARGV.shift || 'process_time'
7
+ n = (ARGV.shift || 200).to_i
8
+
9
+ r = Redis.new
10
+ RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
11
+ RubyProf.start
12
+
13
+ n.times do |i|
14
+ key = "foo#{i}"
15
+ r[key] = key * 10
16
+ r[key]
17
+ end
18
+
19
+ results = RubyProf.stop
20
+ File.open("profile.#{mode}", 'w') do |out|
21
+ RubyProf::CallTreePrinter.new(results).print(out)
22
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{redis}
5
+ s.version = "0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ezra Zygmuntowicz", "Taylor Weibley", "Matthew Clark", "Brian McKinney", "Salvatore Sanfilippo", "Luca Guidi"]
9
+ # s.autorequire = %q{redis-rb}
10
+ s.date = %q{2009-06-23}
11
+ s.description = %q{Ruby client library for redis key value storage server}
12
+ s.email = %q{ez@engineyard.com}
13
+ s.extra_rdoc_files = ["LICENSE"]
14
+ s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/dist_redis.rb", "lib/hash_ring.rb", "lib/pipeline.rb", "lib/redis.rb", "spec/redis_spec.rb", "spec/spec_helper.rb"]
15
+ s.has_rdoc = true
16
+ s.homepage = %q{http://github.com/ezmobius/redis-rb}
17
+ s.require_paths = ["lib"]
18
+ s.rubygems_version = %q{1.3.1}
19
+ s.summary = %q{Ruby client library for redis key value storage server}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 2
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
@@ -0,0 +1,637 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'redis/raketasks'
3
+ require 'logger'
4
+
5
+ class Foo
6
+ attr_accessor :bar
7
+ def initialize(bar)
8
+ @bar = bar
9
+ end
10
+
11
+ def ==(other)
12
+ @bar == other.bar
13
+ end
14
+ end
15
+
16
+ describe "redis" do
17
+ before(:all) do
18
+ result = RedisRunner.start_detached
19
+ raise("Could not start redis-server, aborting") unless result
20
+
21
+ # yea, this sucks, but it seems like sometimes we try to connect too quickly w/o it
22
+ sleep 1
23
+
24
+ # use database 15 for testing so we dont accidentally step on you real data
25
+ @r = Redis.new :db => 15
26
+ end
27
+
28
+ before(:each) do
29
+ @r['foo'] = 'bar'
30
+ end
31
+
32
+ after(:each) do
33
+ @r.keys('*').each {|k| @r.del k}
34
+ end
35
+
36
+ after(:all) do
37
+ begin
38
+ @r.quit
39
+ ensure
40
+ RedisRunner.stop
41
+ end
42
+ end
43
+
44
+ it "should be able connect without a timeout" do
45
+ lambda { Redis.new :timeout => 0 }.should_not raise_error
46
+ end
47
+
48
+ it "should be able to provide a logger" do
49
+ log = StringIO.new
50
+ r = Redis.new :db => 15, :logger => Logger.new(log)
51
+ r.ping
52
+ log.string.should include("ping")
53
+ end
54
+
55
+ it "should be able to PING" do
56
+ @r.ping.should == 'PONG'
57
+ end
58
+
59
+ it "should be able to GET a key" do
60
+ @r['foo'].should == 'bar'
61
+ end
62
+
63
+ it "should be able to SET a key" do
64
+ @r['foo'] = 'nik'
65
+ @r['foo'].should == 'nik'
66
+ end
67
+
68
+ it "should properly handle trailing newline characters" do
69
+ @r['foo'] = "bar\n"
70
+ @r['foo'].should == "bar\n"
71
+ end
72
+
73
+ it "should store and retrieve all possible characters at the beginning and the end of a string" do
74
+ (0..255).each do |char_idx|
75
+ string = "#{char_idx.chr}---#{char_idx.chr}"
76
+ @r['foo'] = string
77
+ @r['foo'].should == string
78
+ end
79
+ end
80
+
81
+ it "should be able to SET a key with an expiry" do
82
+ @r.set('foo', 'bar', 1)
83
+ @r['foo'].should == 'bar'
84
+ sleep 2
85
+ @r['foo'].should == nil
86
+ end
87
+
88
+ it "should be able to return a TTL for a key" do
89
+ @r.set('foo', 'bar', 1)
90
+ @r.ttl('foo').should == 1
91
+ end
92
+
93
+ it "should be able to SETNX" do
94
+ @r['foo'] = 'nik'
95
+ @r['foo'].should == 'nik'
96
+ @r.setnx 'foo', 'bar'
97
+ @r['foo'].should == 'nik'
98
+ end
99
+ #
100
+ it "should be able to GETSET" do
101
+ @r.getset('foo', 'baz').should == 'bar'
102
+ @r['foo'].should == 'baz'
103
+ end
104
+ #
105
+ it "should be able to INCR a key" do
106
+ @r.del('counter')
107
+ @r.incr('counter').should == 1
108
+ @r.incr('counter').should == 2
109
+ @r.incr('counter').should == 3
110
+ end
111
+ #
112
+ it "should be able to INCRBY a key" do
113
+ @r.del('counter')
114
+ @r.incrby('counter', 1).should == 1
115
+ @r.incrby('counter', 2).should == 3
116
+ @r.incrby('counter', 3).should == 6
117
+ end
118
+ #
119
+ it "should be able to DECR a key" do
120
+ @r.del('counter')
121
+ @r.incr('counter').should == 1
122
+ @r.incr('counter').should == 2
123
+ @r.incr('counter').should == 3
124
+ @r.decr('counter').should == 2
125
+ @r.decr('counter', 2).should == 0
126
+ end
127
+ #
128
+ it "should be able to RANDKEY" do
129
+ @r.randkey.should_not be_nil
130
+ end
131
+ #
132
+ it "should be able to RENAME a key" do
133
+ @r.del 'foo'
134
+ @r.del'bar'
135
+ @r['foo'] = 'hi'
136
+ @r.rename 'foo', 'bar'
137
+ @r['bar'].should == 'hi'
138
+ end
139
+ #
140
+ it "should be able to RENAMENX a key" do
141
+ @r.del 'foo'
142
+ @r.del 'bar'
143
+ @r['foo'] = 'hi'
144
+ @r['bar'] = 'ohai'
145
+ @r.renamenx 'foo', 'bar'
146
+ @r['bar'].should == 'ohai'
147
+ end
148
+ #
149
+ it "should be able to get DBSIZE of the database" do
150
+ @r.delete 'foo'
151
+ dbsize_without_foo = @r.dbsize
152
+ @r['foo'] = 0
153
+ dbsize_with_foo = @r.dbsize
154
+
155
+ dbsize_with_foo.should == dbsize_without_foo + 1
156
+ end
157
+ #
158
+ it "should be able to EXPIRE a key" do
159
+ @r['foo'] = 'bar'
160
+ @r.expire 'foo', 1
161
+ @r['foo'].should == "bar"
162
+ sleep 2
163
+ @r['foo'].should == nil
164
+ end
165
+ #
166
+ it "should be able to EXISTS" do
167
+ @r['foo'] = 'nik'
168
+ @r.exists('foo').should be_true
169
+ @r.del 'foo'
170
+ @r.exists('foo').should be_false
171
+ end
172
+ #
173
+ it "should be able to KEYS" do
174
+ @r.keys("f*").each { |key| @r.del key }
175
+ @r['f'] = 'nik'
176
+ @r['fo'] = 'nak'
177
+ @r['foo'] = 'qux'
178
+ @r.keys("f*").sort.should == ['f','fo', 'foo'].sort
179
+ end
180
+ #
181
+ it "should be able to return a random key (RANDOMKEY)" do
182
+ 3.times { @r.exists(@r.randomkey).should be_true }
183
+ end
184
+ #
185
+ it "should be able to check the TYPE of a key" do
186
+ @r['foo'] = 'nik'
187
+ @r.type('foo').should == "string"
188
+ @r.del 'foo'
189
+ @r.type('foo').should == "none"
190
+ end
191
+ #
192
+ it "should be able to push to the head of a list (LPUSH)" do
193
+ @r.lpush "list", 'hello'
194
+ @r.lpush "list", 42
195
+ @r.type('list').should == "list"
196
+ @r.llen('list').should == 2
197
+ @r.lpop('list').should == '42'
198
+ end
199
+ #
200
+ it "should be able to push to the tail of a list (RPUSH)" do
201
+ @r.rpush "list", 'hello'
202
+ @r.type('list').should == "list"
203
+ @r.llen('list').should == 1
204
+ end
205
+ #
206
+ it "should be able to pop the tail of a list (RPOP)" do
207
+ @r.rpush "list", 'hello'
208
+ @r.rpush"list", 'goodbye'
209
+ @r.type('list').should == "list"
210
+ @r.llen('list').should == 2
211
+ @r.rpop('list').should == 'goodbye'
212
+ end
213
+ #
214
+ it "should be able to pop the head of a list (LPOP)" do
215
+ @r.rpush "list", 'hello'
216
+ @r.rpush "list", 'goodbye'
217
+ @r.type('list').should == "list"
218
+ @r.llen('list').should == 2
219
+ @r.lpop('list').should == 'hello'
220
+ end
221
+ #
222
+ it "should be able to get the length of a list (LLEN)" do
223
+ @r.rpush "list", 'hello'
224
+ @r.rpush "list", 'goodbye'
225
+ @r.type('list').should == "list"
226
+ @r.llen('list').should == 2
227
+ end
228
+ #
229
+ it "should be able to get a range of values from a list (LRANGE)" do
230
+ @r.rpush "list", 'hello'
231
+ @r.rpush "list", 'goodbye'
232
+ @r.rpush "list", '1'
233
+ @r.rpush "list", '2'
234
+ @r.rpush "list", '3'
235
+ @r.type('list').should == "list"
236
+ @r.llen('list').should == 5
237
+ @r.lrange('list', 2, -1).should == ['1', '2', '3']
238
+ end
239
+ #
240
+ it "should be able to trim a list (LTRIM)" do
241
+ @r.rpush "list", 'hello'
242
+ @r.rpush "list", 'goodbye'
243
+ @r.rpush "list", '1'
244
+ @r.rpush "list", '2'
245
+ @r.rpush "list", '3'
246
+ @r.type('list').should == "list"
247
+ @r.llen('list').should == 5
248
+ @r.ltrim 'list', 0, 1
249
+ @r.llen('list').should == 2
250
+ @r.lrange('list', 0, -1).should == ['hello', 'goodbye']
251
+ end
252
+ #
253
+ it "should be able to get a value by indexing into a list (LINDEX)" do
254
+ @r.rpush "list", 'hello'
255
+ @r.rpush "list", 'goodbye'
256
+ @r.type('list').should == "list"
257
+ @r.llen('list').should == 2
258
+ @r.lindex('list', 1).should == 'goodbye'
259
+ end
260
+ #
261
+ it "should be able to set a value by indexing into a list (LSET)" do
262
+ @r.rpush "list", 'hello'
263
+ @r.rpush "list", 'hello'
264
+ @r.type('list').should == "list"
265
+ @r.llen('list').should == 2
266
+ @r.lset('list', 1, 'goodbye').should == 'OK'
267
+ @r.lindex('list', 1).should == 'goodbye'
268
+ end
269
+ #
270
+ it "should be able to remove values from a list (LREM)" do
271
+ @r.rpush "list", 'hello'
272
+ @r.rpush "list", 'goodbye'
273
+ @r.type('list').should == "list"
274
+ @r.llen('list').should == 2
275
+ @r.lrem('list', 1, 'hello').should == 1
276
+ @r.lrange('list', 0, -1).should == ['goodbye']
277
+ end
278
+
279
+ it "should be able to pop values from a list and push them onto a temp list(RPOPLPUSH)" do
280
+ @r.rpush "list", 'one'
281
+ @r.rpush "list", 'two'
282
+ @r.rpush "list", 'three'
283
+ @r.type('list').should == "list"
284
+ @r.llen('list').should == 3
285
+ @r.lrange('list',0,-1).should == ['one', 'two', 'three']
286
+ @r.lrange('tmp',0,-1).should == []
287
+ @r.rpoplpush('list', 'tmp').should == 'three'
288
+ @r.lrange('tmp',0,-1).should == ['three']
289
+ @r.rpoplpush('list', 'tmp').should == 'two'
290
+ @r.lrange('tmp',0,-1).should == ['two', 'three']
291
+ @r.rpoplpush('list', 'tmp').should == 'one'
292
+ @r.lrange('tmp',0,-1).should == ['one','two','three']
293
+ end
294
+ #
295
+ it "should be able add members to a set (SADD)" do
296
+ @r.sadd "set", 'key1'
297
+ @r.sadd "set", 'key2'
298
+ @r.type('set').should == "set"
299
+ @r.scard('set').should == 2
300
+ @r.smembers('set').sort.should == ['key1', 'key2'].sort
301
+ end
302
+ #
303
+ it "should be able delete members to a set (SREM)" do
304
+ @r.sadd "set", 'key1'
305
+ @r.sadd "set", 'key2'
306
+ @r.type('set').should == "set"
307
+ @r.scard('set').should == 2
308
+ @r.smembers('set').sort.should == ['key1', 'key2'].sort
309
+ @r.srem('set', 'key1')
310
+ @r.scard('set').should == 1
311
+ @r.smembers('set').should == ['key2']
312
+ end
313
+ #
314
+ it "should be able to return and remove random key from set (SPOP)" do
315
+ @r.sadd "set_pop", "key1"
316
+ @r.sadd "set_pop", "key2"
317
+ @r.spop("set_pop").should_not be_nil
318
+ @r.scard("set_pop").should == 1
319
+ end
320
+ #
321
+ it "should be able to return random key without delete the key from a set (SRANDMEMBER)" do
322
+ @r.sadd "set_srandmember", "key1"
323
+ @r.sadd "set_srandmember", "key2"
324
+ @r.srandmember("set_srandmember").should_not be_nil
325
+ @r.scard("set_srandmember").should == 2
326
+ end
327
+ #
328
+ it "should be able count the members of a set (SCARD)" do
329
+ @r.sadd "set", 'key1'
330
+ @r.sadd "set", 'key2'
331
+ @r.type('set').should == "set"
332
+ @r.scard('set').should == 2
333
+ end
334
+ #
335
+ it "should be able test for set membership (SISMEMBER)" do
336
+ @r.sadd "set", 'key1'
337
+ @r.sadd "set", 'key2'
338
+ @r.type('set').should == "set"
339
+ @r.scard('set').should == 2
340
+ @r.sismember('set', 'key1').should be_true
341
+ @r.sismember('set', 'key2').should be_true
342
+ @r.sismember('set', 'notthere').should be_false
343
+ end
344
+ #
345
+ it "should be able to do set intersection (SINTER)" do
346
+ @r.sadd "set", 'key1'
347
+ @r.sadd "set", 'key2'
348
+ @r.sadd "set2", 'key2'
349
+ @r.sinter('set', 'set2').should == ['key2']
350
+ end
351
+ #
352
+ it "should be able to do set intersection and store the results in a key (SINTERSTORE)" do
353
+ @r.sadd "set", 'key1'
354
+ @r.sadd "set", 'key2'
355
+ @r.sadd "set2", 'key2'
356
+ @r.sinterstore('newone', 'set', 'set2').should == 1
357
+ @r.smembers('newone').should == ['key2']
358
+ end
359
+ #
360
+ it "should be able to do set union (SUNION)" do
361
+ @r.sadd "set", 'key1'
362
+ @r.sadd "set", 'key2'
363
+ @r.sadd "set2", 'key2'
364
+ @r.sadd "set2", 'key3'
365
+ @r.sunion('set', 'set2').sort.should == ['key1','key2','key3'].sort
366
+ end
367
+ #
368
+ it "should be able to do set union and store the results in a key (SUNIONSTORE)" do
369
+ @r.sadd "set", 'key1'
370
+ @r.sadd "set", 'key2'
371
+ @r.sadd "set2", 'key2'
372
+ @r.sadd "set2", 'key3'
373
+ @r.sunionstore('newone', 'set', 'set2').should == 3
374
+ @r.smembers('newone').sort.should == ['key1','key2','key3'].sort
375
+ end
376
+ #
377
+ it "should be able to do set difference (SDIFF)" do
378
+ @r.sadd "set", 'a'
379
+ @r.sadd "set", 'b'
380
+ @r.sadd "set2", 'b'
381
+ @r.sadd "set2", 'c'
382
+ @r.sdiff('set', 'set2').should == ['a']
383
+ end
384
+ #
385
+ it "should be able to do set difference and store the results in a key (SDIFFSTORE)" do
386
+ @r.sadd "set", 'a'
387
+ @r.sadd "set", 'b'
388
+ @r.sadd "set2", 'b'
389
+ @r.sadd "set2", 'c'
390
+ @r.sdiffstore('newone', 'set', 'set2')
391
+ @r.smembers('newone').should == ['a']
392
+ end
393
+ #
394
+ it "should be able move elements from one set to another (SMOVE)" do
395
+ @r.sadd 'set1', 'a'
396
+ @r.sadd 'set1', 'b'
397
+ @r.sadd 'set2', 'x'
398
+ @r.smove('set1', 'set2', 'a').should be_true
399
+ @r.sismember('set2', 'a').should be_true
400
+ @r.delete('set1')
401
+ end
402
+ #
403
+ it "should be able to do crazy SORT queries" do
404
+ # The 'Dogs' is capitialized on purpose
405
+ @r['dog_1'] = 'louie'
406
+ @r.rpush 'Dogs', 1
407
+ @r['dog_2'] = 'lucy'
408
+ @r.rpush 'Dogs', 2
409
+ @r['dog_3'] = 'max'
410
+ @r.rpush 'Dogs', 3
411
+ @r['dog_4'] = 'taj'
412
+ @r.rpush 'Dogs', 4
413
+ @r.sort('Dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie']
414
+ @r.sort('Dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj']
415
+ end
416
+
417
+ it "should be able to handle array of :get using SORT" do
418
+ @r['dog:1:name'] = 'louie'
419
+ @r['dog:1:breed'] = 'mutt'
420
+ @r.rpush 'dogs', 1
421
+ @r['dog:2:name'] = 'lucy'
422
+ @r['dog:2:breed'] = 'poodle'
423
+ @r.rpush 'dogs', 2
424
+ @r['dog:3:name'] = 'max'
425
+ @r['dog:3:breed'] = 'hound'
426
+ @r.rpush 'dogs', 3
427
+ @r['dog:4:name'] = 'taj'
428
+ @r['dog:4:breed'] = 'terrier'
429
+ @r.rpush 'dogs', 4
430
+ @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1]).should == ['louie', 'mutt']
431
+ @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1], :order => 'desc alpha').should == ['taj', 'terrier']
432
+ end
433
+ #
434
+ it "should be able count the members of a zset" do
435
+ @r.set_add "set", 'key1'
436
+ @r.set_add "set", 'key2'
437
+ @r.zset_add 'zset', 1, 'set'
438
+ @r.zset_count('zset').should == 1
439
+ @r.delete('set')
440
+ @r.delete('zset')
441
+ end
442
+ #
443
+ it "should be able add members to a zset" do
444
+ @r.set_add "set", 'key1'
445
+ @r.set_add "set", 'key2'
446
+ @r.zset_add 'zset', 1, 'set'
447
+ @r.zset_range('zset', 0, 1).should == ['set']
448
+ @r.zset_count('zset').should == 1
449
+ @r.delete('set')
450
+ @r.delete('zset')
451
+ end
452
+ #
453
+ it "should be able delete members to a zset" do
454
+ @r.set_add "set", 'key1'
455
+ @r.set_add "set", 'key2'
456
+ @r.type?('set').should == "set"
457
+ @r.set_add "set2", 'key3'
458
+ @r.set_add "set2", 'key4'
459
+ @r.type?('set2').should == "set"
460
+ @r.zset_add 'zset', 1, 'set'
461
+ @r.zset_count('zset').should == 1
462
+ @r.zset_add 'zset', 2, 'set2'
463
+ @r.zset_count('zset').should == 2
464
+ @r.zset_delete 'zset', 'set'
465
+ @r.zset_count('zset').should == 1
466
+ @r.delete('set')
467
+ @r.delete('set2')
468
+ @r.delete('zset')
469
+ end
470
+ #
471
+ it "should be able to get a range of values from a zset" do
472
+ @r.set_add "set", 'key1'
473
+ @r.set_add "set", 'key2'
474
+ @r.set_add "set2", 'key3'
475
+ @r.set_add "set2", 'key4'
476
+ @r.set_add "set3", 'key1'
477
+ @r.type?('set').should == 'set'
478
+ @r.type?('set2').should == 'set'
479
+ @r.type?('set3').should == 'set'
480
+ @r.zset_add 'zset', 1, 'set'
481
+ @r.zset_add 'zset', 2, 'set2'
482
+ @r.zset_add 'zset', 3, 'set3'
483
+ @r.zset_count('zset').should == 3
484
+ @r.zset_range('zset', 0, 3).should == ['set', 'set2', 'set3']
485
+ @r.delete('set')
486
+ @r.delete('set2')
487
+ @r.delete('set3')
488
+ @r.delete('zset')
489
+ end
490
+ #
491
+ it "should be able to get a reverse range of values from a zset" do
492
+ @r.set_add "set", 'key1'
493
+ @r.set_add "set", 'key2'
494
+ @r.set_add "set2", 'key3'
495
+ @r.set_add "set2", 'key4'
496
+ @r.set_add "set3", 'key1'
497
+ @r.type?('set').should == 'set'
498
+ @r.type?('set2').should == 'set'
499
+ @r.type?('set3').should == 'set'
500
+ @r.zset_add 'zset', 1, 'set'
501
+ @r.zset_add 'zset', 2, 'set2'
502
+ @r.zset_add 'zset', 3, 'set3'
503
+ @r.zset_count('zset').should == 3
504
+ @r.zset_reverse_range('zset', 0, 3).should == ['set3', 'set2', 'set']
505
+ @r.delete('set')
506
+ @r.delete('set2')
507
+ @r.delete('set3')
508
+ @r.delete('zset')
509
+ end
510
+ #
511
+ it "should be able to get a range by score of values from a zset" do
512
+ @r.set_add "set", 'key1'
513
+ @r.set_add "set", 'key2'
514
+ @r.set_add "set2", 'key3'
515
+ @r.set_add "set2", 'key4'
516
+ @r.set_add "set3", 'key1'
517
+ @r.set_add "set4", 'key4'
518
+ @r.zset_add 'zset', 1, 'set'
519
+ @r.zset_add 'zset', 2, 'set2'
520
+ @r.zset_add 'zset', 3, 'set3'
521
+ @r.zset_add 'zset', 4, 'set4'
522
+ @r.zset_count('zset').should == 4
523
+ @r.zset_range_by_score('zset', 2, 3).should == ['set2', 'set3']
524
+ @r.delete('set')
525
+ @r.delete('set2')
526
+ @r.delete('set3')
527
+ @r.delete('set4')
528
+ @r.delete('zset')
529
+ end
530
+
531
+ it "should provide info (INFO)" do
532
+ [: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|
533
+ @r.info.keys.should include(x)
534
+ end
535
+ end
536
+ #
537
+ it "should be able to flush the database (FLUSHDB)" do
538
+ @r['key1'] = 'keyone'
539
+ @r['key2'] = 'keytwo'
540
+ @r.keys('*').sort.should == ['foo', 'key1', 'key2'].sort #foo from before
541
+ @r.flushdb
542
+ @r.keys('*').should == []
543
+ end
544
+ #
545
+ it "should raise exception when manually try to change the database" do
546
+ lambda { @r.select(0) }.should raise_error
547
+ end
548
+ #
549
+ it "should be able to provide the last save time (LASTSAVE)" do
550
+ savetime = @r.lastsave
551
+ Time.at(savetime).class.should == Time
552
+ Time.at(savetime).should <= Time.now
553
+ end
554
+
555
+ it "should be able to MGET keys" do
556
+ @r['foo'] = 1000
557
+ @r['bar'] = 2000
558
+ @r.mget('foo', 'bar').should == ['1000', '2000']
559
+ @r.mget('foo', 'bar', 'baz').should == ['1000', '2000', nil]
560
+ end
561
+
562
+ it "should be able to mapped MGET keys" do
563
+ @r['foo'] = 1000
564
+ @r['bar'] = 2000
565
+ @r.mapped_mget('foo', 'bar').should == { 'foo' => '1000', 'bar' => '2000'}
566
+ @r.mapped_mget('foo', 'baz', 'bar').should == { 'foo' => '1000', 'bar' => '2000'}
567
+ end
568
+
569
+ it "should be able to MSET values" do
570
+ @r.mset :key1 => "value1", :key2 => "value2"
571
+ @r['key1'].should == "value1"
572
+ @r['key2'].should == "value2"
573
+ end
574
+
575
+ it "should be able to MSETNX values" do
576
+ @r.msetnx :keynx1 => "valuenx1", :keynx2 => "valuenx2"
577
+ @r.mget('keynx1', 'keynx2').should == ["valuenx1", "valuenx2"]
578
+
579
+ @r["keynx1"] = "value1"
580
+ @r["keynx2"] = "value2"
581
+ @r.msetnx :keynx1 => "valuenx1", :keynx2 => "valuenx2"
582
+ @r.mget('keynx1', 'keynx2').should == ["value1", "value2"]
583
+ end
584
+
585
+ it "should bgsave" do
586
+ @r.bgsave.should == 'OK'
587
+ end
588
+
589
+ it "should be able to ECHO" do
590
+ @r.echo("message in a bottle\n").should == "message in a bottle\n"
591
+ end
592
+
593
+ it "should raise error when invoke MONITOR" do
594
+ lambda { @r.monitor }.should raise_error
595
+ end
596
+
597
+ it "should raise error when invoke SYNC" do
598
+ lambda { @r.sync }.should raise_error
599
+ end
600
+
601
+ it "should handle multiple servers" do
602
+ require 'dist_redis'
603
+ @r = DistRedis.new(:hosts=> ['localhost:6379', '127.0.0.1:6379'], :db => 15)
604
+
605
+ 100.times do |idx|
606
+ @r[idx] = "foo#{idx}"
607
+ end
608
+
609
+ 100.times do |idx|
610
+ @r[idx].should == "foo#{idx}"
611
+ end
612
+ end
613
+
614
+ it "should be able to pipeline writes" do
615
+ @r.pipelined do |pipeline|
616
+ pipeline.lpush 'list', "hello"
617
+ pipeline.lpush 'list', 42
618
+ end
619
+
620
+ @r.type('list').should == "list"
621
+ @r.llen('list').should == 2
622
+ @r.lpop('list').should == '42'
623
+ end
624
+
625
+ it "should do nothing when pipelining no commands" do
626
+ @r.pipelined do |pipeline|
627
+ end
628
+ end
629
+
630
+ it "should AUTH when connecting with a password" do
631
+ r = Redis.new(:password => 'secret')
632
+ r.stub!(:connect_to)
633
+ r.should_receive(:call_command).with(['auth', 'secret'])
634
+ r.connect_to_server
635
+ end
636
+
637
+ end