lunar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/examples/ohm.rb +23 -0
- data/lib/lunar.rb +12 -0
- data/lib/lunar/doc.rb +13 -0
- data/lib/lunar/index.rb +70 -0
- data/lib/lunar/scoring.rb +11 -0
- data/test/helper.rb +13 -0
- data/test/test_lunar.rb +4 -0
- data/test/test_lunar_document.rb +20 -0
- data/test/test_lunar_index.rb +174 -0
- data/test/test_lunar_scoring.rb +26 -0
- data/vendor/nest/nest.rb +7 -0
- data/vendor/redis/.gitignore +9 -0
- data/vendor/redis/LICENSE +20 -0
- data/vendor/redis/README.markdown +120 -0
- data/vendor/redis/Rakefile +75 -0
- data/vendor/redis/benchmarking/logging.rb +62 -0
- data/vendor/redis/benchmarking/pipeline.rb +44 -0
- data/vendor/redis/benchmarking/speed.rb +21 -0
- data/vendor/redis/benchmarking/suite.rb +24 -0
- data/vendor/redis/benchmarking/worker.rb +71 -0
- data/vendor/redis/bin/distredis +33 -0
- data/vendor/redis/examples/basic.rb +15 -0
- data/vendor/redis/examples/dist_redis.rb +43 -0
- data/vendor/redis/examples/incr-decr.rb +17 -0
- data/vendor/redis/examples/list.rb +26 -0
- data/vendor/redis/examples/pubsub.rb +25 -0
- data/vendor/redis/examples/sets.rb +36 -0
- data/vendor/redis/lib/edis.rb +3 -0
- data/vendor/redis/lib/redis.rb +496 -0
- data/vendor/redis/lib/redis/client.rb +265 -0
- data/vendor/redis/lib/redis/dist_redis.rb +118 -0
- data/vendor/redis/lib/redis/distributed.rb +460 -0
- data/vendor/redis/lib/redis/hash_ring.rb +131 -0
- data/vendor/redis/lib/redis/pipeline.rb +13 -0
- data/vendor/redis/lib/redis/raketasks.rb +1 -0
- data/vendor/redis/lib/redis/subscribe.rb +79 -0
- data/vendor/redis/profile.rb +22 -0
- data/vendor/redis/tasks/redis.tasks.rb +140 -0
- data/vendor/redis/test/db/.gitignore +1 -0
- data/vendor/redis/test/distributed_test.rb +1131 -0
- data/vendor/redis/test/redis_test.rb +1134 -0
- data/vendor/redis/test/test.conf +8 -0
- data/vendor/redis/test/test_helper.rb +113 -0
- metadata +127 -0
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class HashRing
|
5
|
+
|
6
|
+
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
7
|
+
|
8
|
+
attr_reader :ring, :sorted_keys, :replicas, :nodes
|
9
|
+
|
10
|
+
# nodes is a list of objects that have a proper to_s representation.
|
11
|
+
# replicas indicates how many virtual points should be used pr. node,
|
12
|
+
# replicas are required to improve the distribution.
|
13
|
+
def initialize(nodes=[], replicas=POINTS_PER_SERVER)
|
14
|
+
@replicas = replicas
|
15
|
+
@ring = {}
|
16
|
+
@nodes = []
|
17
|
+
@sorted_keys = []
|
18
|
+
nodes.each do |node|
|
19
|
+
add_node(node)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Adds a `node` to the hash ring (including a number of replicas).
|
24
|
+
def add_node(node)
|
25
|
+
@nodes << node
|
26
|
+
@replicas.times do |i|
|
27
|
+
key = Zlib.crc32("#{node.id}:#{i}")
|
28
|
+
@ring[key] = node
|
29
|
+
@sorted_keys << key
|
30
|
+
end
|
31
|
+
@sorted_keys.sort!
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_node(node)
|
35
|
+
@nodes.reject!{|n| n.id == node.id}
|
36
|
+
@replicas.times do |i|
|
37
|
+
key = Zlib.crc32("#{node.id}:#{i}")
|
38
|
+
@ring.delete(key)
|
39
|
+
@sorted_keys.reject! {|k| k == key}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# get the node in the hash ring for this key
|
44
|
+
def get_node(key)
|
45
|
+
get_node_pos(key)[0]
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_node_pos(key)
|
49
|
+
return [nil,nil] if @ring.size == 0
|
50
|
+
crc = Zlib.crc32(key)
|
51
|
+
idx = HashRing.binary_search(@sorted_keys, crc)
|
52
|
+
return [@ring[@sorted_keys[idx]], idx]
|
53
|
+
end
|
54
|
+
|
55
|
+
def iter_nodes(key)
|
56
|
+
return [nil,nil] if @ring.size == 0
|
57
|
+
node, pos = get_node_pos(key)
|
58
|
+
@sorted_keys[pos..-1].each do |k|
|
59
|
+
yield @ring[k]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class << self
|
64
|
+
|
65
|
+
# gem install RubyInline to use this code
|
66
|
+
# Native extension to perform the binary search within the hashring.
|
67
|
+
# There's a pure ruby version below so this is purely optional
|
68
|
+
# for performance. In testing 20k gets and sets, the native
|
69
|
+
# binary search shaved about 12% off the runtime (9sec -> 8sec).
|
70
|
+
begin
|
71
|
+
require 'inline'
|
72
|
+
inline do |builder|
|
73
|
+
builder.c <<-EOM
|
74
|
+
int binary_search(VALUE ary, unsigned int r) {
|
75
|
+
int upper = RARRAY_LEN(ary) - 1;
|
76
|
+
int lower = 0;
|
77
|
+
int idx = 0;
|
78
|
+
|
79
|
+
while (lower <= upper) {
|
80
|
+
idx = (lower + upper) / 2;
|
81
|
+
|
82
|
+
VALUE continuumValue = RARRAY_PTR(ary)[idx];
|
83
|
+
unsigned int l = NUM2UINT(continuumValue);
|
84
|
+
if (l == r) {
|
85
|
+
return idx;
|
86
|
+
}
|
87
|
+
else if (l > r) {
|
88
|
+
upper = idx - 1;
|
89
|
+
}
|
90
|
+
else {
|
91
|
+
lower = idx + 1;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
if (upper < 0) {
|
95
|
+
upper = RARRAY_LEN(ary) - 1;
|
96
|
+
}
|
97
|
+
return upper;
|
98
|
+
}
|
99
|
+
EOM
|
100
|
+
end
|
101
|
+
rescue Exception => e
|
102
|
+
# Find the closest index in HashRing with value <= the given value
|
103
|
+
def binary_search(ary, value, &block)
|
104
|
+
upper = ary.size - 1
|
105
|
+
lower = 0
|
106
|
+
idx = 0
|
107
|
+
|
108
|
+
while(lower <= upper) do
|
109
|
+
idx = (lower + upper) / 2
|
110
|
+
comp = ary[idx] <=> value
|
111
|
+
|
112
|
+
if comp == 0
|
113
|
+
return idx
|
114
|
+
elsif comp > 0
|
115
|
+
upper = idx - 1
|
116
|
+
else
|
117
|
+
lower = idx + 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if upper < 0
|
122
|
+
upper = ary.size - 1
|
123
|
+
end
|
124
|
+
return upper
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../../tasks/redis.tasks"
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Redis
|
2
|
+
class SubscribedClient
|
3
|
+
def initialize(client)
|
4
|
+
@client = client
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(*args)
|
8
|
+
@client.process(args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(*channels, &block)
|
12
|
+
subscription("subscribe", "unsubscribe", channels, block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def psubscribe(*channels, &block)
|
16
|
+
subscription("psubscribe", "punsubscribe", channels, block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def unsubscribe(*channels)
|
20
|
+
call(:unsubscribe, *channels)
|
21
|
+
end
|
22
|
+
|
23
|
+
def punsubscribe(*channels)
|
24
|
+
call(:punsubscribe, *channels)
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def subscription(start, stop, channels, block)
|
30
|
+
sub = Subscription.new(&block)
|
31
|
+
|
32
|
+
begin
|
33
|
+
@client.call_loop(start, *channels) do |line|
|
34
|
+
type, *rest = line
|
35
|
+
sub.callbacks[type].call(*rest)
|
36
|
+
break if type == stop && rest.last == 0
|
37
|
+
end
|
38
|
+
ensure
|
39
|
+
send(stop)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Subscription
|
45
|
+
attr :callbacks
|
46
|
+
|
47
|
+
def initialize
|
48
|
+
@callbacks = Hash.new do |hash, key|
|
49
|
+
hash[key] = lambda { |*_| }
|
50
|
+
end
|
51
|
+
|
52
|
+
yield(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def subscribe(&block)
|
56
|
+
@callbacks["subscribe"] = block
|
57
|
+
end
|
58
|
+
|
59
|
+
def unsubscribe(&block)
|
60
|
+
@callbacks["unsubscribe"] = block
|
61
|
+
end
|
62
|
+
|
63
|
+
def message(&block)
|
64
|
+
@callbacks["message"] = block
|
65
|
+
end
|
66
|
+
|
67
|
+
def psubscribe(&block)
|
68
|
+
@callbacks["psubscribe"] = block
|
69
|
+
end
|
70
|
+
|
71
|
+
def punsubscribe(&block)
|
72
|
+
@callbacks["punsubscribe"] = block
|
73
|
+
end
|
74
|
+
|
75
|
+
def pmessage(&block)
|
76
|
+
@callbacks["pmessage"] = block
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -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,140 @@
|
|
1
|
+
# Inspired by rabbitmq.rake the Redbox project at http://github.com/rick/redbox/tree/master
|
2
|
+
require 'rake'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'open-uri'
|
5
|
+
|
6
|
+
class RedisRunner
|
7
|
+
|
8
|
+
def self.redisdir
|
9
|
+
"/tmp/redis/"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.redisconfdir
|
13
|
+
'/etc/redis.conf'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.dtach_socket
|
17
|
+
'/tmp/redis.dtach'
|
18
|
+
end
|
19
|
+
|
20
|
+
# Just check for existance of dtach socket
|
21
|
+
def self.running?
|
22
|
+
File.exists? dtach_socket
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.start
|
26
|
+
puts 'Detach with Ctrl+\ Re-attach with rake redis:attach'
|
27
|
+
sleep 3
|
28
|
+
exec "dtach -A #{dtach_socket} redis-server #{redisconfdir}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.start_detached
|
32
|
+
system "dtach -n #{dtach_socket} redis-server #{redisconfdir}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.attach
|
36
|
+
exec "dtach -a #{dtach_socket}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.stop
|
40
|
+
system 'echo "SHUTDOWN" | nc localhost 6379'
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
namespace :redis do
|
46
|
+
|
47
|
+
desc 'About redis'
|
48
|
+
task :about do
|
49
|
+
puts "\nSee http://code.google.com/p/redis/ for information about redis.\n\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
desc 'Start redis'
|
53
|
+
task :start do
|
54
|
+
RedisRunner.start
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'Stop redis'
|
58
|
+
task :stop do
|
59
|
+
RedisRunner.stop
|
60
|
+
end
|
61
|
+
|
62
|
+
desc 'Restart redis'
|
63
|
+
task :restart do
|
64
|
+
RedisRunner.stop
|
65
|
+
RedisRunner.start
|
66
|
+
end
|
67
|
+
|
68
|
+
desc 'Attach to redis dtach socket'
|
69
|
+
task :attach do
|
70
|
+
RedisRunner.attach
|
71
|
+
end
|
72
|
+
|
73
|
+
desc 'Install the lastest verison of Redis from Github (requires git, duh)'
|
74
|
+
task :install => [:about, :download, :make] do
|
75
|
+
%w(redis-benchmark redis-cli redis-server).each do |bin|
|
76
|
+
sh "sudo cp /tmp/redis/#{bin} /usr/bin/"
|
77
|
+
end
|
78
|
+
|
79
|
+
puts "Installed redis-benchmark, redis-cli and redis-server to /usr/bin/"
|
80
|
+
|
81
|
+
unless File.exists?('/etc/redis.conf')
|
82
|
+
sh 'sudo cp /tmp/redis/redis.conf /etc/'
|
83
|
+
puts "Installed redis.conf to /etc/ \n You should look at this file!"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
task :make do
|
88
|
+
sh "cd #{RedisRunner.redisdir} && make clean"
|
89
|
+
sh "cd #{RedisRunner.redisdir} && make"
|
90
|
+
end
|
91
|
+
|
92
|
+
desc "Download package"
|
93
|
+
task :download do
|
94
|
+
sh 'rm -rf /tmp/redis/' if File.exists?("#{RedisRunner.redisdir}/.svn")
|
95
|
+
sh 'git clone git://github.com/antirez/redis.git /tmp/redis' unless File.exists?(RedisRunner.redisdir)
|
96
|
+
|
97
|
+
if File.exists?("#{RedisRunner.redisdir}/.git")
|
98
|
+
arguments = ENV['COMMIT'].nil? ? "pull" : "reset --hard #{ENV['COMMIT']}"
|
99
|
+
sh "cd #{RedisRunner.redisdir} && git #{arguments}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "Open an IRb session"
|
104
|
+
task :console do
|
105
|
+
RedisRunner.start_detached
|
106
|
+
system "irb -I lib -I extra -r redis.rb"
|
107
|
+
RedisRunner.stop
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
namespace :dtach do
|
112
|
+
|
113
|
+
desc 'About dtach'
|
114
|
+
task :about do
|
115
|
+
puts "\nSee http://dtach.sourceforge.net/ for information about dtach.\n\n"
|
116
|
+
end
|
117
|
+
|
118
|
+
desc 'Install dtach 0.8 from source'
|
119
|
+
task :install => [:about] do
|
120
|
+
|
121
|
+
Dir.chdir('/tmp/')
|
122
|
+
unless File.exists?('/tmp/dtach-0.8.tar.gz')
|
123
|
+
require 'net/http'
|
124
|
+
|
125
|
+
url = 'http://downloads.sourceforge.net/project/dtach/dtach/0.8/dtach-0.8.tar.gz'
|
126
|
+
open('/tmp/dtach-0.8.tar.gz', 'wb') do |file| file.write(open(url).read) end
|
127
|
+
end
|
128
|
+
|
129
|
+
unless File.directory?('/tmp/dtach-0.8')
|
130
|
+
system('tar xzf dtach-0.8.tar.gz')
|
131
|
+
end
|
132
|
+
|
133
|
+
Dir.chdir('/tmp/dtach-0.8/')
|
134
|
+
sh 'cd /tmp/dtach-0.8/ && ./configure && make'
|
135
|
+
sh 'sudo cp /tmp/dtach-0.8/dtach /usr/bin/'
|
136
|
+
|
137
|
+
puts 'Dtach successfully installed to /usr/bin.'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
redis.pid
|
@@ -0,0 +1,1131 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
require "redis/distributed"
|
4
|
+
|
5
|
+
class RedisDistributedTest < Test::Unit::TestCase
|
6
|
+
NODES = ["redis://127.0.0.1:6379/15"]
|
7
|
+
|
8
|
+
setup do
|
9
|
+
@log = StringIO.new
|
10
|
+
@r = Redis::Distributed.new NODES, :logger => ::Logger.new(@log)
|
11
|
+
ensure_redis_running(@r)
|
12
|
+
@r.flushdb
|
13
|
+
end
|
14
|
+
|
15
|
+
context "Internals" do
|
16
|
+
test "Logger" do
|
17
|
+
@r.ping
|
18
|
+
|
19
|
+
assert_match(/Redis >> PING/, @log.string)
|
20
|
+
end
|
21
|
+
|
22
|
+
test "Recovers from failed commands" do
|
23
|
+
# See http://github.com/ezmobius/redis-rb/issues#issue/28
|
24
|
+
|
25
|
+
assert_raises ArgumentError do
|
26
|
+
@r.srem "foo"
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_nothing_raised do
|
30
|
+
@r.info
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "Connection handling" do
|
36
|
+
test "PING" do
|
37
|
+
assert_equal ["PONG"], @r.ping
|
38
|
+
end
|
39
|
+
|
40
|
+
test "SELECT" do
|
41
|
+
@r.set "foo", "bar"
|
42
|
+
|
43
|
+
@r.select 14
|
44
|
+
assert_equal nil, @r.get("foo")
|
45
|
+
|
46
|
+
@r.select 15
|
47
|
+
|
48
|
+
assert_equal "bar", @r.get("foo")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "Commands operating on all the kind of values" do
|
53
|
+
test "EXISTS" do
|
54
|
+
assert_equal false, @r.exists("foo")
|
55
|
+
|
56
|
+
@r.set("foo", "s1")
|
57
|
+
|
58
|
+
assert_equal true, @r.exists("foo")
|
59
|
+
end
|
60
|
+
|
61
|
+
test "DEL" do
|
62
|
+
@r.set "foo", "s1"
|
63
|
+
@r.set "bar", "s2"
|
64
|
+
@r.set "baz", "s3"
|
65
|
+
|
66
|
+
assert_equal ["bar", "baz", "foo"], @r.keys("*").sort
|
67
|
+
|
68
|
+
@r.del "foo"
|
69
|
+
|
70
|
+
assert_equal ["bar", "baz"], @r.keys("*").sort
|
71
|
+
|
72
|
+
@r.del "bar", "baz"
|
73
|
+
|
74
|
+
assert_equal [], @r.keys("*").sort
|
75
|
+
end
|
76
|
+
|
77
|
+
test "TYPE" do
|
78
|
+
assert_equal "none", @r.type("foo")
|
79
|
+
|
80
|
+
@r.set("foo", "s1")
|
81
|
+
|
82
|
+
assert_equal "string", @r.type("foo")
|
83
|
+
end
|
84
|
+
|
85
|
+
test "KEYS" do
|
86
|
+
@r.set("f", "s1")
|
87
|
+
@r.set("fo", "s2")
|
88
|
+
@r.set("foo", "s3")
|
89
|
+
|
90
|
+
assert_equal ["f","fo", "foo"], @r.keys("f*").sort
|
91
|
+
end
|
92
|
+
|
93
|
+
test "RANDOMKEY" do
|
94
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
95
|
+
@r.randomkey
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
test "RENAME" do
|
100
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
101
|
+
@r.set("foo", "s1")
|
102
|
+
@r.rename "foo", "bar"
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_equal "s1", @r.get("foo")
|
106
|
+
assert_equal nil, @r.get("bar")
|
107
|
+
end
|
108
|
+
|
109
|
+
test "RENAMENX" do
|
110
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
111
|
+
@r.set("foo", "s1")
|
112
|
+
@r.rename "foo", "bar"
|
113
|
+
end
|
114
|
+
|
115
|
+
assert_equal "s1", @r.get("foo")
|
116
|
+
assert_equal nil, @r.get("bar")
|
117
|
+
end
|
118
|
+
|
119
|
+
test "DBSIZE" do
|
120
|
+
assert_equal [0], @r.dbsize
|
121
|
+
|
122
|
+
@r.set("foo", "s1")
|
123
|
+
|
124
|
+
assert_equal [1], @r.dbsize
|
125
|
+
end
|
126
|
+
|
127
|
+
test "EXPIRE" do
|
128
|
+
@r.set("foo", "s1")
|
129
|
+
@r.expire("foo", 1)
|
130
|
+
|
131
|
+
assert_equal "s1", @r.get("foo")
|
132
|
+
|
133
|
+
sleep 2
|
134
|
+
|
135
|
+
assert_equal nil, @r.get("foo")
|
136
|
+
end
|
137
|
+
|
138
|
+
test "EXPIREAT" do
|
139
|
+
@r.set("foo", "s1")
|
140
|
+
@r.expireat("foo", Time.now.to_i + 1)
|
141
|
+
|
142
|
+
assert_equal "s1", @r.get("foo")
|
143
|
+
|
144
|
+
sleep 2
|
145
|
+
|
146
|
+
assert_equal nil, @r.get("foo")
|
147
|
+
end
|
148
|
+
|
149
|
+
test "TTL" do
|
150
|
+
@r.set("foo", "s1")
|
151
|
+
@r.expire("foo", 1)
|
152
|
+
|
153
|
+
assert_equal 1, @r.ttl("foo")
|
154
|
+
end
|
155
|
+
|
156
|
+
test "MOVE"
|
157
|
+
|
158
|
+
test "FLUSHDB" do
|
159
|
+
@r.set("foo", "s1")
|
160
|
+
@r.set("bar", "s2")
|
161
|
+
|
162
|
+
assert_equal [2], @r.dbsize
|
163
|
+
|
164
|
+
@r.flushdb
|
165
|
+
|
166
|
+
assert_equal [0], @r.dbsize
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "Commands requiring clustering" do
|
171
|
+
test "RENAME" do
|
172
|
+
@r.set("{qux}foo", "s1")
|
173
|
+
@r.rename "{qux}foo", "{qux}bar"
|
174
|
+
|
175
|
+
assert_equal "s1", @r.get("{qux}bar")
|
176
|
+
assert_equal nil, @r.get("{qux}foo")
|
177
|
+
end
|
178
|
+
|
179
|
+
test "RENAMENX" do
|
180
|
+
@r.set("{qux}foo", "s1")
|
181
|
+
@r.set("{qux}bar", "s2")
|
182
|
+
|
183
|
+
assert_equal false, @r.renamenx("{qux}foo", "{qux}bar")
|
184
|
+
|
185
|
+
assert_equal "s1", @r.get("{qux}foo")
|
186
|
+
assert_equal "s2", @r.get("{qux}bar")
|
187
|
+
end
|
188
|
+
|
189
|
+
test "RPOPLPUSH" do
|
190
|
+
@r.rpush "{qux}foo", "s1"
|
191
|
+
@r.rpush "{qux}foo", "s2"
|
192
|
+
|
193
|
+
assert_equal "s2", @r.rpoplpush("{qux}foo", "{qux}bar")
|
194
|
+
assert_equal ["s2"], @r.lrange("{qux}bar", 0, -1)
|
195
|
+
assert_equal "s1", @r.rpoplpush("{qux}foo", "{qux}bar")
|
196
|
+
assert_equal ["s1", "s2"], @r.lrange("{qux}bar", 0, -1)
|
197
|
+
end
|
198
|
+
|
199
|
+
test "SMOVE" do
|
200
|
+
@r.sadd "{qux}foo", "s1"
|
201
|
+
@r.sadd "{qux}bar", "s2"
|
202
|
+
|
203
|
+
assert @r.smove("{qux}foo", "{qux}bar", "s1")
|
204
|
+
assert @r.sismember("{qux}bar", "s1")
|
205
|
+
end
|
206
|
+
|
207
|
+
test "SINTER" do
|
208
|
+
@r.sadd "{qux}foo", "s1"
|
209
|
+
@r.sadd "{qux}foo", "s2"
|
210
|
+
@r.sadd "{qux}bar", "s2"
|
211
|
+
|
212
|
+
assert_equal ["s2"], @r.sinter("{qux}foo", "{qux}bar")
|
213
|
+
end
|
214
|
+
|
215
|
+
test "SINTERSTORE" do
|
216
|
+
@r.sadd "{qux}foo", "s1"
|
217
|
+
@r.sadd "{qux}foo", "s2"
|
218
|
+
@r.sadd "{qux}bar", "s2"
|
219
|
+
|
220
|
+
@r.sinterstore("{qux}baz", "{qux}foo", "{qux}bar")
|
221
|
+
|
222
|
+
assert_equal ["s2"], @r.smembers("{qux}baz")
|
223
|
+
end
|
224
|
+
|
225
|
+
test "SUNION" do
|
226
|
+
@r.sadd "{qux}foo", "s1"
|
227
|
+
@r.sadd "{qux}foo", "s2"
|
228
|
+
@r.sadd "{qux}bar", "s2"
|
229
|
+
@r.sadd "{qux}bar", "s3"
|
230
|
+
|
231
|
+
assert_equal ["s1", "s2", "s3"], @r.sunion("{qux}foo", "{qux}bar").sort
|
232
|
+
end
|
233
|
+
|
234
|
+
test "SUNIONSTORE" do
|
235
|
+
@r.sadd "{qux}foo", "s1"
|
236
|
+
@r.sadd "{qux}foo", "s2"
|
237
|
+
@r.sadd "{qux}bar", "s2"
|
238
|
+
@r.sadd "{qux}bar", "s3"
|
239
|
+
|
240
|
+
@r.sunionstore("{qux}baz", "{qux}foo", "{qux}bar")
|
241
|
+
|
242
|
+
assert_equal ["s1", "s2", "s3"], @r.smembers("{qux}baz").sort
|
243
|
+
end
|
244
|
+
|
245
|
+
test "SDIFF" do
|
246
|
+
@r.sadd "{qux}foo", "s1"
|
247
|
+
@r.sadd "{qux}foo", "s2"
|
248
|
+
@r.sadd "{qux}bar", "s2"
|
249
|
+
@r.sadd "{qux}bar", "s3"
|
250
|
+
|
251
|
+
assert_equal ["s1"], @r.sdiff("{qux}foo", "{qux}bar")
|
252
|
+
assert_equal ["s3"], @r.sdiff("{qux}bar", "{qux}foo")
|
253
|
+
end
|
254
|
+
|
255
|
+
test "SDIFFSTORE" do
|
256
|
+
@r.sadd "{qux}foo", "s1"
|
257
|
+
@r.sadd "{qux}foo", "s2"
|
258
|
+
@r.sadd "{qux}bar", "s2"
|
259
|
+
@r.sadd "{qux}bar", "s3"
|
260
|
+
|
261
|
+
@r.sdiffstore("{qux}baz", "{qux}foo", "{qux}bar")
|
262
|
+
|
263
|
+
assert_equal ["s1"], @r.smembers("{qux}baz")
|
264
|
+
end
|
265
|
+
|
266
|
+
test "SORT" do
|
267
|
+
@r.set("foo:1", "s1")
|
268
|
+
@r.set("foo:2", "s2")
|
269
|
+
|
270
|
+
@r.rpush("{qux}bar", "1")
|
271
|
+
@r.rpush("{qux}bar", "2")
|
272
|
+
|
273
|
+
assert_equal ["s1"], @r.sort("{qux}bar", :get => "foo:*", :limit => [0, 1])
|
274
|
+
assert_equal ["s2"], @r.sort("{qux}bar", :get => "foo:*", :limit => [0, 1], :order => "desc alpha")
|
275
|
+
end
|
276
|
+
|
277
|
+
test "SORT with an array of GETs" do
|
278
|
+
@r.set("foo:1:a", "s1a")
|
279
|
+
@r.set("foo:1:b", "s1b")
|
280
|
+
|
281
|
+
@r.set("foo:2:a", "s2a")
|
282
|
+
@r.set("foo:2:b", "s2b")
|
283
|
+
|
284
|
+
@r.rpush("{qux}bar", "1")
|
285
|
+
@r.rpush("{qux}bar", "2")
|
286
|
+
|
287
|
+
assert_equal ["s1a", "s1b"], @r.sort("{qux}bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1])
|
288
|
+
assert_equal ["s2a", "s2b"], @r.sort("{qux}bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1], :order => "desc alpha")
|
289
|
+
end
|
290
|
+
|
291
|
+
test "SORT with STORE" do
|
292
|
+
@r.set("foo:1", "s1")
|
293
|
+
@r.set("foo:2", "s2")
|
294
|
+
|
295
|
+
@r.rpush("{qux}bar", "1")
|
296
|
+
@r.rpush("{qux}bar", "2")
|
297
|
+
|
298
|
+
@r.sort("{qux}bar", :get => "foo:*", :store => "{qux}baz")
|
299
|
+
assert_equal ["s1", "s2"], @r.lrange("{qux}baz", 0, -1)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
context "Commands operating on string values" do
|
304
|
+
test "SET and GET" do
|
305
|
+
@r.set("foo", "s1")
|
306
|
+
|
307
|
+
assert_equal "s1", @r.get("foo")
|
308
|
+
end
|
309
|
+
|
310
|
+
test "SET and GET with brackets" do
|
311
|
+
@r["foo"] = "s1"
|
312
|
+
|
313
|
+
assert_equal "s1", @r["foo"]
|
314
|
+
end
|
315
|
+
|
316
|
+
test "SET and GET with newline characters" do
|
317
|
+
@r.set("foo", "1\n")
|
318
|
+
|
319
|
+
assert_equal "1\n", @r.get("foo")
|
320
|
+
end
|
321
|
+
|
322
|
+
test "SET and GET with ASCII characters" do
|
323
|
+
(0..255).each do |i|
|
324
|
+
str = "#{i.chr}---#{i.chr}"
|
325
|
+
@r.set("foo", str)
|
326
|
+
|
327
|
+
assert_equal str, @r.get("foo")
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
test "SETEX" do
|
332
|
+
@r.setex("foo", 1, "s1")
|
333
|
+
|
334
|
+
assert_equal "s1", @r.get("foo")
|
335
|
+
|
336
|
+
sleep 2
|
337
|
+
|
338
|
+
assert_equal nil, @r.get("foo")
|
339
|
+
end
|
340
|
+
|
341
|
+
test "GETSET" do
|
342
|
+
@r.set("foo", "bar")
|
343
|
+
|
344
|
+
assert_equal "bar", @r.getset("foo", "baz")
|
345
|
+
assert_equal "baz", @r.get("foo")
|
346
|
+
end
|
347
|
+
|
348
|
+
test "MGET" do
|
349
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
350
|
+
@r.mget("foo", "bar")
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
test "MGET mapped" do
|
355
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
356
|
+
@r.mapped_mget("foo", "bar")
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
test "SETNX" do
|
361
|
+
@r.set("foo", "s1")
|
362
|
+
|
363
|
+
assert_equal "s1", @r.get("foo")
|
364
|
+
|
365
|
+
@r.setnx("foo", "s2")
|
366
|
+
|
367
|
+
assert_equal "s1", @r.get("foo")
|
368
|
+
end
|
369
|
+
|
370
|
+
test "MSET" do
|
371
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
372
|
+
@r.mset(:foo, "s1", :bar, "s2")
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
test "MSET mapped" do
|
377
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
378
|
+
@r.mapped_mset(:foo => "s1", :bar => "s2")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
test "MSETNX" do
|
383
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
384
|
+
@r.set("foo", "s1")
|
385
|
+
@r.msetnx(:foo, "s2", :bar, "s3")
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
test "MSETNX mapped" do
|
390
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
391
|
+
@r.set("foo", "s1")
|
392
|
+
@r.mapped_msetnx(:foo => "s2", :bar => "s3")
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
test "INCR" do
|
397
|
+
assert_equal 1, @r.incr("foo")
|
398
|
+
assert_equal 2, @r.incr("foo")
|
399
|
+
assert_equal 3, @r.incr("foo")
|
400
|
+
end
|
401
|
+
|
402
|
+
test "INCRBY" do
|
403
|
+
assert_equal 1, @r.incrby("foo", 1)
|
404
|
+
assert_equal 3, @r.incrby("foo", 2)
|
405
|
+
assert_equal 6, @r.incrby("foo", 3)
|
406
|
+
end
|
407
|
+
|
408
|
+
test "DECR" do
|
409
|
+
@r.set("foo", 3)
|
410
|
+
|
411
|
+
assert_equal 2, @r.decr("foo")
|
412
|
+
assert_equal 1, @r.decr("foo")
|
413
|
+
assert_equal 0, @r.decr("foo")
|
414
|
+
end
|
415
|
+
|
416
|
+
test "DECRBY" do
|
417
|
+
@r.set("foo", 6)
|
418
|
+
|
419
|
+
assert_equal 3, @r.decrby("foo", 3)
|
420
|
+
assert_equal 1, @r.decrby("foo", 2)
|
421
|
+
assert_equal 0, @r.decrby("foo", 1)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
context "Commands operating on lists" do
|
426
|
+
test "RPUSH" do
|
427
|
+
@r.rpush "foo", "s1"
|
428
|
+
@r.rpush "foo", "s2"
|
429
|
+
|
430
|
+
assert_equal 2, @r.llen("foo")
|
431
|
+
assert_equal "s2", @r.rpop("foo")
|
432
|
+
end
|
433
|
+
|
434
|
+
test "LPUSH" do
|
435
|
+
@r.lpush "foo", "s1"
|
436
|
+
@r.lpush "foo", "s2"
|
437
|
+
|
438
|
+
assert_equal 2, @r.llen("foo")
|
439
|
+
assert_equal "s2", @r.lpop("foo")
|
440
|
+
end
|
441
|
+
|
442
|
+
test "LLEN" do
|
443
|
+
@r.rpush "foo", "s1"
|
444
|
+
@r.rpush "foo", "s2"
|
445
|
+
|
446
|
+
assert_equal 2, @r.llen("foo")
|
447
|
+
end
|
448
|
+
|
449
|
+
test "LRANGE" do
|
450
|
+
@r.rpush "foo", "s1"
|
451
|
+
@r.rpush "foo", "s2"
|
452
|
+
@r.rpush "foo", "s3"
|
453
|
+
|
454
|
+
assert_equal ["s2", "s3"], @r.lrange("foo", 1, -1)
|
455
|
+
assert_equal ["s1", "s2"], @r.lrange("foo", 0, 1)
|
456
|
+
end
|
457
|
+
|
458
|
+
test "LTRIM" do
|
459
|
+
@r.rpush "foo", "s1"
|
460
|
+
@r.rpush "foo", "s2"
|
461
|
+
@r.rpush "foo", "s3"
|
462
|
+
|
463
|
+
@r.ltrim "foo", 0, 1
|
464
|
+
|
465
|
+
assert_equal 2, @r.llen("foo")
|
466
|
+
assert_equal ["s1", "s2"], @r.lrange("foo", 0, -1)
|
467
|
+
end
|
468
|
+
|
469
|
+
test "LINDEX" do
|
470
|
+
@r.rpush "foo", "s1"
|
471
|
+
@r.rpush "foo", "s2"
|
472
|
+
|
473
|
+
assert_equal "s1", @r.lindex("foo", 0)
|
474
|
+
assert_equal "s2", @r.lindex("foo", 1)
|
475
|
+
end
|
476
|
+
|
477
|
+
test "LSET" do
|
478
|
+
@r.rpush "foo", "s1"
|
479
|
+
@r.rpush "foo", "s2"
|
480
|
+
|
481
|
+
assert_equal "s2", @r.lindex("foo", 1)
|
482
|
+
assert @r.lset("foo", 1, "s3")
|
483
|
+
assert_equal "s3", @r.lindex("foo", 1)
|
484
|
+
|
485
|
+
assert_raises RuntimeError do
|
486
|
+
@r.lset("foo", 4, "s3")
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
test "LREM" do
|
491
|
+
@r.rpush "foo", "s1"
|
492
|
+
@r.rpush "foo", "s2"
|
493
|
+
|
494
|
+
assert_equal 1, @r.lrem("foo", 1, "s1")
|
495
|
+
assert_equal ["s2"], @r.lrange("foo", 0, -1)
|
496
|
+
end
|
497
|
+
|
498
|
+
test "LPOP" do
|
499
|
+
@r.rpush "foo", "s1"
|
500
|
+
@r.rpush "foo", "s2"
|
501
|
+
|
502
|
+
assert_equal 2, @r.llen("foo")
|
503
|
+
assert_equal "s1", @r.lpop("foo")
|
504
|
+
assert_equal 1, @r.llen("foo")
|
505
|
+
end
|
506
|
+
|
507
|
+
test "RPOP" do
|
508
|
+
@r.rpush "foo", "s1"
|
509
|
+
@r.rpush "foo", "s2"
|
510
|
+
|
511
|
+
assert_equal 2, @r.llen("foo")
|
512
|
+
assert_equal "s2", @r.rpop("foo")
|
513
|
+
assert_equal 1, @r.llen("foo")
|
514
|
+
end
|
515
|
+
|
516
|
+
test "RPOPLPUSH" do
|
517
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
518
|
+
@r.rpoplpush("foo", "bar")
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
context "Blocking commands" do
|
524
|
+
test "BLPOP" do
|
525
|
+
@r.lpush("foo", "s1")
|
526
|
+
@r.lpush("foo", "s2")
|
527
|
+
|
528
|
+
thread = Thread.new do
|
529
|
+
redis = Redis::Distributed.new(NODES)
|
530
|
+
sleep 0.3
|
531
|
+
redis.lpush("foo", "s3")
|
532
|
+
end
|
533
|
+
|
534
|
+
assert_equal @r.blpop("foo", 0.1), ["foo", "s2"]
|
535
|
+
assert_equal @r.blpop("foo", 0.1), ["foo", "s1"]
|
536
|
+
assert_equal @r.blpop("foo", 0.4), ["foo", "s3"]
|
537
|
+
|
538
|
+
thread.join
|
539
|
+
end
|
540
|
+
|
541
|
+
test "BRPOP" do
|
542
|
+
@r.rpush("foo", "s1")
|
543
|
+
@r.rpush("foo", "s2")
|
544
|
+
|
545
|
+
t = Thread.new do
|
546
|
+
redis = Redis::Distributed.new(NODES)
|
547
|
+
sleep 0.3
|
548
|
+
redis.rpush("foo", "s3")
|
549
|
+
end
|
550
|
+
|
551
|
+
assert_equal @r.brpop("foo", 0.1), ["foo", "s2"]
|
552
|
+
assert_equal @r.brpop("foo", 0.1), ["foo", "s1"]
|
553
|
+
assert_equal @r.brpop("foo", 0.4), ["foo", "s3"]
|
554
|
+
|
555
|
+
t.join
|
556
|
+
end
|
557
|
+
|
558
|
+
test "BRPOP should unset a configured socket timeout" do
|
559
|
+
@r = Redis::Distributed.new(NODES, :timeout => 1)
|
560
|
+
assert_nothing_raised do
|
561
|
+
@r.brpop("foo", 2)
|
562
|
+
end # Errno::EAGAIN raised if socket times out before redis command times out
|
563
|
+
end
|
564
|
+
|
565
|
+
test "BRPOP should restore the timeout after the command is run"
|
566
|
+
|
567
|
+
test "BRPOP should restore the timeout even if the command fails"
|
568
|
+
end
|
569
|
+
|
570
|
+
context "Commands operating on sets" do
|
571
|
+
test "SADD" do
|
572
|
+
@r.sadd "foo", "s1"
|
573
|
+
@r.sadd "foo", "s2"
|
574
|
+
|
575
|
+
assert_equal ["s1", "s2"], @r.smembers("foo").sort
|
576
|
+
end
|
577
|
+
|
578
|
+
test "SREM" do
|
579
|
+
@r.sadd "foo", "s1"
|
580
|
+
@r.sadd "foo", "s2"
|
581
|
+
|
582
|
+
@r.srem("foo", "s1")
|
583
|
+
|
584
|
+
assert_equal ["s2"], @r.smembers("foo")
|
585
|
+
end
|
586
|
+
|
587
|
+
test "SPOP" do
|
588
|
+
@r.sadd "foo", "s1"
|
589
|
+
@r.sadd "foo", "s2"
|
590
|
+
|
591
|
+
assert ["s1", "s2"].include?(@r.spop("foo"))
|
592
|
+
assert ["s1", "s2"].include?(@r.spop("foo"))
|
593
|
+
assert_nil @r.spop("foo")
|
594
|
+
end
|
595
|
+
|
596
|
+
test "SMOVE" do
|
597
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
598
|
+
@r.sadd "foo", "s1"
|
599
|
+
@r.sadd "bar", "s2"
|
600
|
+
|
601
|
+
@r.smove("foo", "bar", "s1")
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
test "SCARD" do
|
606
|
+
assert_equal 0, @r.scard("foo")
|
607
|
+
|
608
|
+
@r.sadd "foo", "s1"
|
609
|
+
|
610
|
+
assert_equal 1, @r.scard("foo")
|
611
|
+
|
612
|
+
@r.sadd "foo", "s2"
|
613
|
+
|
614
|
+
assert_equal 2, @r.scard("foo")
|
615
|
+
end
|
616
|
+
|
617
|
+
test "SISMEMBER" do
|
618
|
+
assert_equal false, @r.sismember("foo", "s1")
|
619
|
+
|
620
|
+
@r.sadd "foo", "s1"
|
621
|
+
|
622
|
+
assert_equal true, @r.sismember("foo", "s1")
|
623
|
+
assert_equal false, @r.sismember("foo", "s2")
|
624
|
+
end
|
625
|
+
|
626
|
+
test "SINTER" do
|
627
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
628
|
+
@r.sadd "foo", "s1"
|
629
|
+
@r.sadd "foo", "s2"
|
630
|
+
@r.sadd "bar", "s2"
|
631
|
+
|
632
|
+
@r.sinter("foo", "bar")
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
test "SINTERSTORE" do
|
637
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
638
|
+
@r.sadd "foo", "s1"
|
639
|
+
@r.sadd "foo", "s2"
|
640
|
+
@r.sadd "bar", "s2"
|
641
|
+
|
642
|
+
@r.sinterstore("baz", "foo", "bar")
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
test "SUNION" do
|
647
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
648
|
+
@r.sadd "foo", "s1"
|
649
|
+
@r.sadd "foo", "s2"
|
650
|
+
@r.sadd "bar", "s2"
|
651
|
+
@r.sadd "bar", "s3"
|
652
|
+
|
653
|
+
@r.sunion("foo", "bar")
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
test "SUNIONSTORE" do
|
658
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
659
|
+
@r.sadd "foo", "s1"
|
660
|
+
@r.sadd "foo", "s2"
|
661
|
+
@r.sadd "bar", "s2"
|
662
|
+
@r.sadd "bar", "s3"
|
663
|
+
|
664
|
+
@r.sunionstore("baz", "foo", "bar")
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
test "SDIFF" do
|
669
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
670
|
+
@r.sadd "foo", "s1"
|
671
|
+
@r.sadd "foo", "s2"
|
672
|
+
@r.sadd "bar", "s2"
|
673
|
+
@r.sadd "bar", "s3"
|
674
|
+
|
675
|
+
@r.sdiff("foo", "bar")
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
test "SDIFFSTORE" do
|
680
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
681
|
+
@r.sadd "foo", "s1"
|
682
|
+
@r.sadd "foo", "s2"
|
683
|
+
@r.sadd "bar", "s2"
|
684
|
+
@r.sadd "bar", "s3"
|
685
|
+
|
686
|
+
@r.sdiffstore("baz", "foo", "bar")
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
test "SMEMBERS" do
|
691
|
+
assert_equal [], @r.smembers("foo")
|
692
|
+
|
693
|
+
@r.sadd "foo", "s1"
|
694
|
+
@r.sadd "foo", "s2"
|
695
|
+
|
696
|
+
assert_equal ["s1", "s2"], @r.smembers("foo").sort
|
697
|
+
end
|
698
|
+
|
699
|
+
test "SRANDMEMBER" do
|
700
|
+
@r.sadd "foo", "s1"
|
701
|
+
@r.sadd "foo", "s2"
|
702
|
+
|
703
|
+
4.times do
|
704
|
+
assert ["s1", "s2"].include?(@r.srandmember("foo"))
|
705
|
+
end
|
706
|
+
|
707
|
+
assert_equal 2, @r.scard("foo")
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
711
|
+
context "Commands operating on sorted sets" do
|
712
|
+
test "ZADD" do
|
713
|
+
assert_equal 0, @r.zcard("foo")
|
714
|
+
|
715
|
+
@r.zadd "foo", 1, "s1"
|
716
|
+
|
717
|
+
assert_equal 1, @r.zcard("foo")
|
718
|
+
end
|
719
|
+
|
720
|
+
test "ZREM" do
|
721
|
+
@r.zadd "foo", 1, "s1"
|
722
|
+
|
723
|
+
assert_equal 1, @r.zcard("foo")
|
724
|
+
|
725
|
+
@r.zadd "foo", 2, "s2"
|
726
|
+
|
727
|
+
assert_equal 2, @r.zcard("foo")
|
728
|
+
|
729
|
+
@r.zrem "foo", "s1"
|
730
|
+
|
731
|
+
assert_equal 1, @r.zcard("foo")
|
732
|
+
end
|
733
|
+
|
734
|
+
test "ZINCRBY" do
|
735
|
+
@r.zincrby "foo", 1, "s1"
|
736
|
+
|
737
|
+
assert_equal "1", @r.zscore("foo", "s1")
|
738
|
+
|
739
|
+
@r.zincrby "foo", 10, "s1"
|
740
|
+
|
741
|
+
assert_equal "11", @r.zscore("foo", "s1")
|
742
|
+
end
|
743
|
+
|
744
|
+
test "ZRANK"
|
745
|
+
|
746
|
+
test "ZREVRANK"
|
747
|
+
|
748
|
+
test "ZRANGE" do
|
749
|
+
@r.zadd "foo", 1, "s1"
|
750
|
+
@r.zadd "foo", 2, "s2"
|
751
|
+
@r.zadd "foo", 3, "s3"
|
752
|
+
|
753
|
+
assert_equal ["s1", "s2"], @r.zrange("foo", 0, 1)
|
754
|
+
assert_equal ["s1", "1", "s2", "2"], @r.zrange("foo", 0, 1, true)
|
755
|
+
end
|
756
|
+
|
757
|
+
test "ZREVRANGE" do
|
758
|
+
@r.zadd "foo", 1, "s1"
|
759
|
+
@r.zadd "foo", 2, "s2"
|
760
|
+
@r.zadd "foo", 3, "s3"
|
761
|
+
|
762
|
+
assert_equal ["s3", "s2"], @r.zrevrange("foo", 0, 1)
|
763
|
+
assert_equal ["s3", "3", "s2", "2"], @r.zrevrange("foo", 0, 1, true)
|
764
|
+
end
|
765
|
+
|
766
|
+
test "ZRANGEBYSCORE" do
|
767
|
+
@r.zadd "foo", 1, "s1"
|
768
|
+
@r.zadd "foo", 2, "s2"
|
769
|
+
@r.zadd "foo", 3, "s3"
|
770
|
+
|
771
|
+
assert_equal ["s2", "s3"], @r.zrangebyscore("foo", 2, 3)
|
772
|
+
end
|
773
|
+
|
774
|
+
test "ZRANGEBYSCORE with LIMIT"
|
775
|
+
test "ZRANGEBYSCORE with WITHSCORES"
|
776
|
+
|
777
|
+
test "ZCARD" do
|
778
|
+
assert_equal 0, @r.zcard("foo")
|
779
|
+
|
780
|
+
@r.zadd "foo", 1, "s1"
|
781
|
+
|
782
|
+
assert_equal 1, @r.zcard("foo")
|
783
|
+
end
|
784
|
+
|
785
|
+
test "ZSCORE" do
|
786
|
+
@r.zadd "foo", 1, "s1"
|
787
|
+
|
788
|
+
assert_equal "1", @r.zscore("foo", "s1")
|
789
|
+
|
790
|
+
assert_nil @r.zscore("foo", "s2")
|
791
|
+
assert_nil @r.zscore("bar", "s1")
|
792
|
+
end
|
793
|
+
|
794
|
+
test "ZREMRANGEBYRANK"
|
795
|
+
|
796
|
+
test "ZREMRANGEBYSCORE"
|
797
|
+
|
798
|
+
test "ZUNION"
|
799
|
+
|
800
|
+
test "ZINTER"
|
801
|
+
end
|
802
|
+
|
803
|
+
context "Commands operating on hashes" do
|
804
|
+
test "HSET and HGET" do
|
805
|
+
@r.hset("foo", "f1", "s1")
|
806
|
+
|
807
|
+
assert_equal "s1", @r.hget("foo", "f1")
|
808
|
+
end
|
809
|
+
|
810
|
+
test "HDEL" do
|
811
|
+
@r.hset("foo", "f1", "s1")
|
812
|
+
|
813
|
+
assert_equal "s1", @r.hget("foo", "f1")
|
814
|
+
|
815
|
+
@r.hdel("foo", "f1")
|
816
|
+
|
817
|
+
assert_equal nil, @r.hget("foo", "f1")
|
818
|
+
end
|
819
|
+
|
820
|
+
test "HEXISTS" do
|
821
|
+
assert_equal false, @r.hexists("foo", "f1")
|
822
|
+
|
823
|
+
@r.hset("foo", "f1", "s1")
|
824
|
+
|
825
|
+
assert @r.hexists("foo", "f1")
|
826
|
+
end
|
827
|
+
|
828
|
+
test "HLEN" do
|
829
|
+
assert_equal 0, @r.hlen("foo")
|
830
|
+
|
831
|
+
@r.hset("foo", "f1", "s1")
|
832
|
+
|
833
|
+
assert_equal 1, @r.hlen("foo")
|
834
|
+
|
835
|
+
@r.hset("foo", "f2", "s2")
|
836
|
+
|
837
|
+
assert_equal 2, @r.hlen("foo")
|
838
|
+
end
|
839
|
+
|
840
|
+
test "HKEYS" do
|
841
|
+
assert_equal [], @r.hkeys("foo")
|
842
|
+
|
843
|
+
@r.hset("foo", "f1", "s1")
|
844
|
+
@r.hset("foo", "f2", "s2")
|
845
|
+
|
846
|
+
assert_equal ["f1", "f2"], @r.hkeys("foo")
|
847
|
+
end
|
848
|
+
|
849
|
+
test "HVALS" do
|
850
|
+
assert_equal [], @r.hvals("foo")
|
851
|
+
|
852
|
+
@r.hset("foo", "f1", "s1")
|
853
|
+
@r.hset("foo", "f2", "s2")
|
854
|
+
|
855
|
+
assert_equal ["s1", "s2"], @r.hvals("foo")
|
856
|
+
end
|
857
|
+
|
858
|
+
test "HGETALL" do
|
859
|
+
assert_equal({}, @r.hgetall("foo"))
|
860
|
+
|
861
|
+
@r.hset("foo", "f1", "s1")
|
862
|
+
@r.hset("foo", "f2", "s2")
|
863
|
+
|
864
|
+
assert_equal({"f1" => "s1", "f2" => "s2"}, @r.hgetall("foo"))
|
865
|
+
end
|
866
|
+
|
867
|
+
test "HMSET" do
|
868
|
+
@r.hmset("hash", "foo1", "bar1", "foo2", "bar2")
|
869
|
+
|
870
|
+
assert_equal "bar1", @r.hget("hash", "foo1")
|
871
|
+
assert_equal "bar2", @r.hget("hash", "foo2")
|
872
|
+
end
|
873
|
+
|
874
|
+
test "HMSET with invalid arguments" do
|
875
|
+
assert_raises RuntimeError do
|
876
|
+
@r.hmset("hash", "foo1", "bar1", "foo2", "bar2", "foo3")
|
877
|
+
end
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
context "Sorting" do
|
882
|
+
test "SORT" do
|
883
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
884
|
+
@r.set("foo:1", "s1")
|
885
|
+
@r.set("foo:2", "s2")
|
886
|
+
|
887
|
+
@r.rpush("bar", "1")
|
888
|
+
@r.rpush("bar", "2")
|
889
|
+
|
890
|
+
@r.sort("bar", :get => "foo:*", :limit => [0, 1])
|
891
|
+
end
|
892
|
+
end
|
893
|
+
end
|
894
|
+
|
895
|
+
context "Transactions" do
|
896
|
+
test "MULTI/DISCARD" do
|
897
|
+
@foo = nil
|
898
|
+
|
899
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
900
|
+
@r.multi { @foo = 1 }
|
901
|
+
end
|
902
|
+
|
903
|
+
assert_nil @foo
|
904
|
+
|
905
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
906
|
+
@r.discard
|
907
|
+
end
|
908
|
+
end
|
909
|
+
end
|
910
|
+
|
911
|
+
context "Publish/Subscribe" do
|
912
|
+
|
913
|
+
test "SUBSCRIBE and UNSUBSCRIBE"
|
914
|
+
# do
|
915
|
+
# thread = Thread.new do
|
916
|
+
# @r.subscribe("foo") do |on|
|
917
|
+
# on.subscribe do |channel, total|
|
918
|
+
# @subscribed = true
|
919
|
+
# @t1 = total
|
920
|
+
# end
|
921
|
+
|
922
|
+
# on.message do |channel, message|
|
923
|
+
# if message == "s1"
|
924
|
+
# @r.unsubscribe
|
925
|
+
# @message = message
|
926
|
+
# end
|
927
|
+
# end
|
928
|
+
|
929
|
+
# on.unsubscribe do |channel, total|
|
930
|
+
# @unsubscribed = true
|
931
|
+
# @t2 = total
|
932
|
+
# end
|
933
|
+
# end
|
934
|
+
# end
|
935
|
+
|
936
|
+
# Redis::Distributed.new(NODES).publish("foo", "s1")
|
937
|
+
|
938
|
+
# thread.join
|
939
|
+
|
940
|
+
# assert @subscribed
|
941
|
+
# assert_equal 1, @t1
|
942
|
+
# assert @unsubscribed
|
943
|
+
# assert_equal 0, @t2
|
944
|
+
# assert_equal "s1", @message
|
945
|
+
# end
|
946
|
+
|
947
|
+
test "SUBSCRIBE within SUBSCRIBE"
|
948
|
+
# do
|
949
|
+
# @channels = []
|
950
|
+
|
951
|
+
# thread = Thread.new do
|
952
|
+
# @r.subscribe("foo") do |on|
|
953
|
+
# on.subscribe do |channel, total|
|
954
|
+
# @channels << channel
|
955
|
+
|
956
|
+
# @r.subscribe("bar") if channel == "foo"
|
957
|
+
# @r.unsubscribe if channel == "bar"
|
958
|
+
# end
|
959
|
+
# end
|
960
|
+
# end
|
961
|
+
|
962
|
+
# Redis::Distributed.new(NODES).publish("foo", "s1")
|
963
|
+
|
964
|
+
# thread.join
|
965
|
+
|
966
|
+
# assert_equal ["foo", "bar"], @channels
|
967
|
+
# end
|
968
|
+
|
969
|
+
test "other commands within a SUBSCRIBE"
|
970
|
+
# do
|
971
|
+
# assert_raises RuntimeError do
|
972
|
+
# @r.subscribe("foo") do |on|
|
973
|
+
# on.subscribe do |channel, total|
|
974
|
+
# @r.set("bar", "s2")
|
975
|
+
# end
|
976
|
+
# end
|
977
|
+
# end
|
978
|
+
# end
|
979
|
+
|
980
|
+
test "SUBSCRIBE without a block"
|
981
|
+
# do
|
982
|
+
# assert_raises LocalJumpError do
|
983
|
+
# @r.subscribe("foo")
|
984
|
+
# end
|
985
|
+
# end
|
986
|
+
|
987
|
+
test "SUBSCRIBE timeout"
|
988
|
+
|
989
|
+
end
|
990
|
+
|
991
|
+
context "Persistence control commands" do
|
992
|
+
test "SAVE and BGSAVE" do
|
993
|
+
assert_nothing_raised do
|
994
|
+
@r.save
|
995
|
+
end
|
996
|
+
|
997
|
+
assert_nothing_raised do
|
998
|
+
@r.bgsave
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
test "LASTSAVE" do
|
1003
|
+
assert @r.lastsave.all? { |t| Time.at(t) <= Time.now }
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
context "Remote server control commands" do
|
1008
|
+
test "INFO" do
|
1009
|
+
%w(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|
|
1010
|
+
@r.info.each do |info|
|
1011
|
+
assert info.keys.include?(x)
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
test "MONITOR" do
|
1017
|
+
assert_raises NotImplementedError do
|
1018
|
+
@r.monitor
|
1019
|
+
end
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
test "ECHO" do
|
1023
|
+
assert_equal ["foo bar baz\n"], @r.echo("foo bar baz\n")
|
1024
|
+
end
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
context "Distributed" do
|
1028
|
+
test "handle multiple servers" do
|
1029
|
+
@r = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
|
1030
|
+
|
1031
|
+
100.times do |idx|
|
1032
|
+
@r.set(idx.to_s, "foo#{idx}")
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
100.times do |idx|
|
1036
|
+
assert_equal "foo#{idx}", @r.get(idx.to_s)
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
assert_equal "0", @r.keys("*").sort.first
|
1040
|
+
assert_equal "string", @r.type("1")
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
test "add nodes" do
|
1044
|
+
logger = Logger.new("/dev/null")
|
1045
|
+
|
1046
|
+
@r = Redis::Distributed.new NODES, :logger => logger, :timeout => 10
|
1047
|
+
|
1048
|
+
assert_equal "127.0.0.1", @r.nodes[0].client.host
|
1049
|
+
assert_equal 6379, @r.nodes[0].client.port
|
1050
|
+
assert_equal 15, @r.nodes[0].client.db
|
1051
|
+
assert_equal 10, @r.nodes[0].client.timeout
|
1052
|
+
assert_equal logger, @r.nodes[0].client.logger
|
1053
|
+
|
1054
|
+
@r.add_node("redis://localhost:6380/14")
|
1055
|
+
|
1056
|
+
assert_equal "localhost", @r.nodes[1].client.host
|
1057
|
+
assert_equal 6380, @r.nodes[1].client.port
|
1058
|
+
assert_equal 14, @r.nodes[1].client.db
|
1059
|
+
assert_equal 10, @r.nodes[1].client.timeout
|
1060
|
+
assert_equal logger, @r.nodes[1].client.logger
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
context "Pipelining commands" do
|
1065
|
+
test "cannot be distributed" do
|
1066
|
+
assert_raises Redis::Distributed::CannotDistribute do
|
1067
|
+
@r.pipelined do
|
1068
|
+
@r.lpush "foo", "s1"
|
1069
|
+
@r.lpush "foo", "s2"
|
1070
|
+
end
|
1071
|
+
end
|
1072
|
+
end
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
context "Unknown commands" do
|
1076
|
+
should "not work by default" do
|
1077
|
+
assert_raises NoMethodError do
|
1078
|
+
@r.not_yet_implemented_command
|
1079
|
+
end
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
context "Key tags" do
|
1084
|
+
should "hash consistently" do
|
1085
|
+
r1 = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
|
1086
|
+
r2 = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
|
1087
|
+
r3 = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
|
1088
|
+
|
1089
|
+
assert r1.node_for("foo").id == r2.node_for("foo").id
|
1090
|
+
assert r1.node_for("foo").id == r3.node_for("foo").id
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
should "allow clustering of keys" do
|
1094
|
+
@r = Redis::Distributed.new(NODES)
|
1095
|
+
@r.add_node("redis://localhost:6379/14")
|
1096
|
+
@r.flushdb
|
1097
|
+
|
1098
|
+
100.times do |i|
|
1099
|
+
@r.set "{foo}users:#{i}", i
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
assert_equal [0, 100], @r.nodes.map { |node| node.keys.size }
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
should "distribute keys if no clustering is used" do
|
1106
|
+
@r.add_node("redis://localhost:6379/14")
|
1107
|
+
@r.flushdb
|
1108
|
+
|
1109
|
+
@r.set "users:1", 1
|
1110
|
+
@r.set "users:4", 4
|
1111
|
+
|
1112
|
+
assert_equal [1, 1], @r.nodes.map { |node| node.keys.size }
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
should "allow passing a custom tag extractor" do
|
1116
|
+
@r = Redis::Distributed.new(NODES, :tag => /^(.+?):/)
|
1117
|
+
@r.add_node("redis://localhost:6379/14")
|
1118
|
+
@r.flushdb
|
1119
|
+
|
1120
|
+
100.times do |i|
|
1121
|
+
@r.set "foo:users:#{i}", i
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
assert_equal [0, 100], @r.nodes.map { |node| node.keys.size }
|
1125
|
+
end
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
teardown do
|
1129
|
+
@r.nodes.each { |node| node.client.disconnect }
|
1130
|
+
end
|
1131
|
+
end
|