lunar 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +53 -0
  6. data/VERSION +1 -0
  7. data/examples/ohm.rb +23 -0
  8. data/lib/lunar.rb +12 -0
  9. data/lib/lunar/doc.rb +13 -0
  10. data/lib/lunar/index.rb +70 -0
  11. data/lib/lunar/scoring.rb +11 -0
  12. data/test/helper.rb +13 -0
  13. data/test/test_lunar.rb +4 -0
  14. data/test/test_lunar_document.rb +20 -0
  15. data/test/test_lunar_index.rb +174 -0
  16. data/test/test_lunar_scoring.rb +26 -0
  17. data/vendor/nest/nest.rb +7 -0
  18. data/vendor/redis/.gitignore +9 -0
  19. data/vendor/redis/LICENSE +20 -0
  20. data/vendor/redis/README.markdown +120 -0
  21. data/vendor/redis/Rakefile +75 -0
  22. data/vendor/redis/benchmarking/logging.rb +62 -0
  23. data/vendor/redis/benchmarking/pipeline.rb +44 -0
  24. data/vendor/redis/benchmarking/speed.rb +21 -0
  25. data/vendor/redis/benchmarking/suite.rb +24 -0
  26. data/vendor/redis/benchmarking/worker.rb +71 -0
  27. data/vendor/redis/bin/distredis +33 -0
  28. data/vendor/redis/examples/basic.rb +15 -0
  29. data/vendor/redis/examples/dist_redis.rb +43 -0
  30. data/vendor/redis/examples/incr-decr.rb +17 -0
  31. data/vendor/redis/examples/list.rb +26 -0
  32. data/vendor/redis/examples/pubsub.rb +25 -0
  33. data/vendor/redis/examples/sets.rb +36 -0
  34. data/vendor/redis/lib/edis.rb +3 -0
  35. data/vendor/redis/lib/redis.rb +496 -0
  36. data/vendor/redis/lib/redis/client.rb +265 -0
  37. data/vendor/redis/lib/redis/dist_redis.rb +118 -0
  38. data/vendor/redis/lib/redis/distributed.rb +460 -0
  39. data/vendor/redis/lib/redis/hash_ring.rb +131 -0
  40. data/vendor/redis/lib/redis/pipeline.rb +13 -0
  41. data/vendor/redis/lib/redis/raketasks.rb +1 -0
  42. data/vendor/redis/lib/redis/subscribe.rb +79 -0
  43. data/vendor/redis/profile.rb +22 -0
  44. data/vendor/redis/tasks/redis.tasks.rb +140 -0
  45. data/vendor/redis/test/db/.gitignore +1 -0
  46. data/vendor/redis/test/distributed_test.rb +1131 -0
  47. data/vendor/redis/test/redis_test.rb +1134 -0
  48. data/vendor/redis/test/test.conf +8 -0
  49. data/vendor/redis/test/test_helper.rb +113 -0
  50. 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,13 @@
1
+ class Redis
2
+ class Pipeline
3
+ attr :commands
4
+
5
+ def initialize
6
+ @commands = []
7
+ end
8
+
9
+ def call(*args)
10
+ @commands << args
11
+ end
12
+ end
13
+ 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