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