require File.dirname(__FILE__) + '/spec_helper' describe "redis" do @redis_version = Gem::Version.new(Redis.current.info["redis_version"]) before(:all) do # use database 15 for testing so we dont accidentally step on your real data @redis = Redis.new :db => 15 end before(:each) do @namespaced = Redis::Namespace.new(:ns, :redis => @redis) @namespaced.flushdb @redis['foo'] = 'bar' end after(:each) do @redis.flushdb end after(:all) do @redis.quit end it "proxies `client` to the client" do @namespaced.client.should eq(@redis.client) end it "should be able to use a namespace" do @namespaced['foo'].should eq(nil) @namespaced['foo'] = 'chris' @namespaced['foo'].should eq('chris') @redis['foo'] = 'bob' @redis['foo'].should eq('bob') @namespaced.incrby('counter', 2) @namespaced['counter'].to_i.should eq(2) @redis['counter'].should eq(nil) @namespaced.type('counter').should eq('string') end context 'when sending capital commands (issue 68)' do it 'should be able to use a namespace' do @namespaced.send('SET', 'fubar', 'quux') @redis.get('fubar').should be_nil @namespaced.get('fubar').should eq 'quux' end end it "should be able to use a namespace with bpop" do @namespaced.rpush "foo", "string" @namespaced.rpush "foo", "ns:string" @namespaced.rpush "foo", "string_no_timeout" @namespaced.blpop("foo", 1).should eq(["foo", "string"]) @namespaced.blpop("foo", 1).should eq(["foo", "ns:string"]) @namespaced.blpop("foo").should eq(["foo", "string_no_timeout"]) @namespaced.blpop("foo", 1).should eq(nil) end it "should be able to use a namespace with del" do @namespaced['foo'] = 1000 @namespaced['bar'] = 2000 @namespaced['baz'] = 3000 @namespaced.del 'foo' @namespaced['foo'].should eq(nil) @namespaced.del 'bar', 'baz' @namespaced['bar'].should eq(nil) @namespaced['baz'].should eq(nil) end it 'should be able to use a namespace with append' do @namespaced['foo'] = 'bar' @namespaced.append('foo','n').should eq(4) @namespaced['foo'].should eq('barn') @redis['foo'].should eq('bar') end it 'should be able to use a namespace with brpoplpush' do @namespaced.lpush('foo','bar') @namespaced.brpoplpush('foo','bar',0).should eq('bar') @namespaced.lrange('foo',0,-1).should eq([]) @namespaced.lrange('bar',0,-1).should eq(['bar']) end it 'should be able to use a namespace with getbit' do @namespaced.set('foo','bar') @namespaced.getbit('foo',1).should eq(1) end it 'should be able to use a namespace with getrange' do @namespaced.set('foo','bar') @namespaced.getrange('foo',0,-1).should eq('bar') end it 'should be able to use a namespace with linsert' do @namespaced.rpush('foo','bar') @namespaced.rpush('foo','barn') @namespaced.rpush('foo','bart') @namespaced.linsert('foo','BEFORE','barn','barf').should eq(4) @namespaced.lrange('foo',0,-1).should eq(['bar','barf','barn','bart']) end it 'should be able to use a namespace with lpushx' do @namespaced.lpushx('foo','bar').should eq(0) @namespaced.lpush('foo','boo') @namespaced.lpushx('foo','bar').should eq(2) @namespaced.lrange('foo',0,-1).should eq(['bar','boo']) end it 'should be able to use a namespace with rpushx' do @namespaced.rpushx('foo','bar').should eq(0) @namespaced.lpush('foo','boo') @namespaced.rpushx('foo','bar').should eq(2) @namespaced.lrange('foo',0,-1).should eq(['boo','bar']) end it 'should be able to use a namespace with setbit' do @namespaced.setbit('virgin_key', 1, 1) @namespaced.exists('virgin_key').should be_true @namespaced.get('virgin_key').should eq(@namespaced.getrange('virgin_key',0,-1)) end it 'should be able to use a namespace with setrange' do @namespaced.setrange('foo', 0, 'bar') @namespaced['foo'].should eq('bar') @namespaced.setrange('bar', 2, 'foo') @namespaced['bar'].should eq("\000\000foo") end it "should be able to use a namespace with mget" do @namespaced['foo'] = 1000 @namespaced['bar'] = 2000 @namespaced.mapped_mget('foo', 'bar').should eq({ 'foo' => '1000', 'bar' => '2000' }) @namespaced.mapped_mget('foo', 'baz', 'bar').should eq({'foo'=>'1000', 'bar'=>'2000', 'baz' => nil}) end it "should be able to use a namespace with mset" do @namespaced.mset('foo', '1000', 'bar', '2000') @namespaced.mapped_mget('foo', 'bar').should eq({ 'foo' => '1000', 'bar' => '2000' }) @namespaced.mapped_mget('foo', 'baz', 'bar').should eq({ 'foo' => '1000', 'bar' => '2000', 'baz' => nil}) @namespaced.mapped_mset('foo' => '3000', 'bar' => '5000') @namespaced.mapped_mget('foo', 'bar').should eq({ 'foo' => '3000', 'bar' => '5000' }) @namespaced.mapped_mget('foo', 'baz', 'bar').should eq({ 'foo' => '3000', 'bar' => '5000', 'baz' => nil}) end it "should be able to use a namespace with msetnx" do @namespaced.msetnx('foo', '1000', 'bar', '2000') @namespaced.mapped_mget('foo', 'bar').should eq({ 'foo' => '1000', 'bar' => '2000' }) @namespaced.mapped_mget('foo', 'baz', 'bar').should eq({ 'foo' => '1000', 'bar' => '2000', 'baz' => nil}) end it "should be able to use a namespace with mapped_msetnx" do @namespaced.set('foo','1') @namespaced.mapped_msetnx('foo'=>'1000', 'bar'=>'2000').should be_false @namespaced.mapped_mget('foo', 'bar').should eq({ 'foo' => '1', 'bar' => nil }) @namespaced.mapped_msetnx('bar'=>'2000', 'baz'=>'1000').should be_true @namespaced.mapped_mget('foo', 'bar').should eq({ 'foo' => '1', 'bar' => '2000' }) end it "should be able to use a namespace with hashes" do @namespaced.hset('foo', 'key', 'value') @namespaced.hset('foo', 'key1', 'value1') @namespaced.hget('foo', 'key').should eq('value') @namespaced.hgetall('foo').should eq({'key' => 'value', 'key1' => 'value1'}) @namespaced.hlen('foo').should eq(2) @namespaced.hkeys('foo').should eq(['key', 'key1']) @namespaced.hmset('bar', 'key', 'value', 'key1', 'value1') @namespaced.hmget('bar', 'key', 'key1') @namespaced.hmset('bar', 'a_number', 1) @namespaced.hmget('bar', 'a_number').should eq(['1']) @namespaced.hincrby('bar', 'a_number', 3) @namespaced.hmget('bar', 'a_number').should eq(['4']) @namespaced.hgetall('bar').should eq({'key' => 'value', 'key1' => 'value1', 'a_number' => '4'}) @namespaced.hsetnx('foonx','nx',10).should be_true @namespaced.hsetnx('foonx','nx',12).should be_false @namespaced.hget('foonx','nx').should eq("10") @namespaced.hkeys('foonx').should eq(%w{ nx }) @namespaced.hvals('foonx').should eq(%w{ 10 }) @namespaced.mapped_hmset('baz', {'key' => 'value', 'key1' => 'value1', 'a_number' => 4}) @namespaced.mapped_hmget('baz', 'key', 'key1', 'a_number').should eq({'key' => 'value', 'key1' => 'value1', 'a_number' => '4'}) @namespaced.hgetall('baz').should eq({'key' => 'value', 'key1' => 'value1', 'a_number' => '4'}) end it "should properly intersect three sets" do @namespaced.sadd('foo', 1) @namespaced.sadd('foo', 2) @namespaced.sadd('foo', 3) @namespaced.sadd('bar', 2) @namespaced.sadd('bar', 3) @namespaced.sadd('bar', 4) @namespaced.sadd('baz', 3) @namespaced.sinter('foo', 'bar', 'baz').should eq(%w( 3 )) end it "should properly union two sets" do @namespaced.sadd('foo', 1) @namespaced.sadd('foo', 2) @namespaced.sadd('bar', 2) @namespaced.sadd('bar', 3) @namespaced.sadd('bar', 4) @namespaced.sunion('foo', 'bar').sort.should eq(%w( 1 2 3 4 )) end it "should properly union two sorted sets with options" do @namespaced.zadd('sort1', 1, 1) @namespaced.zadd('sort1', 2, 2) @namespaced.zadd('sort2', 2, 2) @namespaced.zadd('sort2', 3, 3) @namespaced.zadd('sort2', 4, 4) @namespaced.zunionstore('union', ['sort1', 'sort2'], :weights => [2, 1]) @namespaced.zrevrange('union', 0, -1).should eq(%w( 2 4 3 1 )) end it "should properly union two sorted sets without options" do @namespaced.zadd('sort1', 1, 1) @namespaced.zadd('sort1', 2, 2) @namespaced.zadd('sort2', 2, 2) @namespaced.zadd('sort2', 3, 3) @namespaced.zadd('sort2', 4, 4) @namespaced.zunionstore('union', ['sort1', 'sort2']) @namespaced.zrevrange('union', 0, -1).should eq(%w( 4 2 3 1 )) end it "should add namespace to sort" do @namespaced.sadd('foo', 1) @namespaced.sadd('foo', 2) @namespaced.set('weight_1', 2) @namespaced.set('weight_2', 1) @namespaced.set('value_1', 'a') @namespaced.set('value_2', 'b') @namespaced.sort('foo').should eq(%w( 1 2 )) @namespaced.sort('foo', :limit => [0, 1]).should eq(%w( 1 )) @namespaced.sort('foo', :order => 'desc').should eq(%w( 2 1 )) @namespaced.sort('foo', :by => 'weight_*').should eq(%w( 2 1 )) @namespaced.sort('foo', :get => 'value_*').should eq(%w( a b )) @namespaced.sort('foo', :get => '#').should eq(%w( 1 2 )) @namespaced.sort('foo', :get => ['#', 'value_*']).should eq([["1", "a"], ["2", "b"]]) @namespaced.sort('foo', :store => 'result') @namespaced.lrange('result', 0, -1).should eq(%w( 1 2 )) end it "should yield the correct list of keys" do @namespaced["foo"] = 1 @namespaced["bar"] = 2 @namespaced["baz"] = 3 @namespaced.keys("*").sort.should eq(%w( bar baz foo )) @namespaced.keys.sort.should eq(%w( bar baz foo )) end it "should add namepsace to multi blocks" do @namespaced.mapped_hmset "foo", {"key" => "value"} @namespaced.multi do |r| r.del "foo" r.mapped_hmset "foo", {"key1" => "value1"} end @namespaced.hgetall("foo").should eq({"key1" => "value1"}) end it "should pass through multi commands without block" do @namespaced.mapped_hmset "foo", {"key" => "value"} @namespaced.multi @namespaced.del "foo" @namespaced.mapped_hmset "foo", {"key1" => "value1"} @namespaced.exec @namespaced.hgetall("foo").should eq({"key1" => "value1"}) end it 'should return futures without attempting to remove namespaces' do @namespaced.multi do @future = @namespaced.keys('*') end @future.class.should be(Redis::Future) end it "should add namespace to pipelined blocks" do @namespaced.mapped_hmset "foo", {"key" => "value"} @namespaced.pipelined do |r| r.del "foo" r.mapped_hmset "foo", {"key1" => "value1"} end @namespaced.hgetall("foo").should eq({"key1" => "value1"}) end it "should returned response array from pipelined block" do @namespaced.mset "foo", "bar", "key", "value" result = @namespaced.pipelined do |r| r["foo"] r["key"] end result.should eq(["bar", "value"]) end it "should add namespace to strlen" do @namespaced.set("mykey", "123456") @namespaced.strlen("mykey").should eq(6) end it "should not add namespace to echo" do @namespaced.echo(123).should eq("123") end it "can change its namespace" do @namespaced['foo'].should eq(nil) @namespaced['foo'] = 'chris' @namespaced['foo'].should eq('chris') @namespaced.namespace.should eq(:ns) @namespaced.namespace = :spec @namespaced.namespace.should eq(:spec) @namespaced['foo'].should eq(nil) @namespaced['foo'] = 'chris' @namespaced['foo'].should eq('chris') end it "can accept a temporary namespace" do @namespaced.namespace.should eq(:ns) @namespaced['foo'].should eq(nil) @namespaced.namespace(:spec) do |temp_ns| temp_ns.namespace.should eq(:spec) temp_ns['foo'].should eq(nil) temp_ns['foo'] = 'jake' temp_ns['foo'].should eq('jake') end @namespaced.namespace.should eq(:ns) @namespaced['foo'].should eq(nil) end it "should respond to :namespace=" do @namespaced.respond_to?(:namespace=).should eq(true) end it "should respond to :warning=" do @namespaced.respond_to?(:warning=).should == true end it "should warn against unknown commands if :warning is true" do @namespaced.warning = true capture_stderr { @namespaced.unknown('foo') }.should == "Passing 'unknown' command to redis as is." end # Redis 2.6 RC reports its version as 2.5. if @redis_version >= Gem::Version.new("2.5.0") describe "redis 2.6 commands" do it "should namespace bitcount" do pending "awaiting implementaton of command in redis gem" end it "should namespace bitop" do pending "awaiting implementaton of command in redis gem" end it "should namespace dump" do pending "awaiting implementaton of command in redis gem" end it "should namespace hincrbyfloat" do @namespaced.hset('mykey', 'field', 10.50) @namespaced.hincrbyfloat('mykey', 'field', 0.1).should eq(10.6) end it "should namespace incrbyfloat" do @namespaced.set('mykey', 10.50) @namespaced.incrbyfloat('mykey', 0.1).should eq(10.6) end it "should namespace object" do @namespaced.set('foo', 1000) @namespaced.object('encoding', 'foo').should eq('int') end it "should namespace persist" do @namespaced.set('mykey', 'Hello') @namespaced.expire('mykey', 60) @namespaced.persist('mykey').should eq(true) @namespaced.ttl('mykey').should eq(-1) end it "should namespace pexpire" do @namespaced.set('mykey', 'Hello') @namespaced.pexpire('mykey', 60000).should eq(true) end it "should namespace pexpireat" do @namespaced.set('mykey', 'Hello') @namespaced.pexpire('mykey', 1555555555005).should eq(true) end it "should namespace psetex" do @namespaced.psetex('mykey', 10000, 'Hello').should eq('OK') @namespaced.get('mykey').should eq('Hello') end it "should namespace pttl" do @namespaced.set('mykey', 'Hello') @namespaced.expire('mykey', 1) @namespaced.pttl('mykey').should >= 0 end it "should namespace restore" do pending "awaiting implementaton of command in redis gem" end it "should namespace eval keys passed in as array args" do @namespaced. eval("return {KEYS[1], KEYS[2]}", %w[k1 k2], %w[arg1 arg2]). should eq(%w[ns:k1 ns:k2]) end it "should namespace eval keys passed in as hash args" do @namespaced. eval("return {KEYS[1], KEYS[2]}", :keys => %w[k1 k2], :argv => %w[arg1 arg2]). should eq(%w[ns:k1 ns:k2]) end context '#evalsha' do let!(:sha) do @namespaced.script(:load, "return {KEYS[1], KEYS[2]}") end it "should namespace evalsha keys passed in as array args" do @namespaced. evalsha(sha, %w[k1 k2], %w[arg1 arg2]). should eq(%w[ns:k1 ns:k2]) end it "should namespace evalsha keys passed in as hash args" do @namespaced. evalsha(sha, :keys => %w[k1 k2], :argv => %w[arg1 arg2]). should eq(%w[ns:k1 ns:k2]) end end context "in a nested namespace" do let(:nested_namespace) { Redis::Namespace.new(:nest, :redis => @namespaced) } let(:sha) { nested_namespace.script(:load, "return {KEYS[1], KEYS[2]}") } it "should namespace eval keys passed in as hash args" do nested_namespace. eval("return {KEYS[1], KEYS[2]}", :keys => %w[k1 k2], :argv => %w[arg1 arg2]). should eq(%w[ns:nest:k1 ns:nest:k2]) end it "should namespace evalsha keys passed in as hash args" do nested_namespace.evalsha(sha, :keys => %w[k1 k2], :argv => %w[arg1 arg2]). should eq(%w[ns:nest:k1 ns:nest:k2]) end end end end # Redis 2.8 RC reports its version as 2.7. if @redis_version >= Gem::Version.new("2.7.105") describe "redis 2.8 commands" do context 'keyspace scan methods' do let(:keys) do %w(alpha ns:beta gamma ns:delta ns:epsilon ns:zeta:one ns:zeta:two ns:theta) end let(:namespaced_keys) do keys.map{|k| k.dup.sub!(/\Ans:/,'') }.compact.sort end before(:each) do keys.each do |key| @redis.set(key, key) end end let(:matching_namespaced_keys) do namespaced_keys.select{|k| k[/\Azeta:/] }.compact.sort end context '#scan' do context 'when :match supplied' do it 'should retrieve the proper keys' do _, result = @namespaced.scan(0, :match => 'zeta:*', :count => 1000) result.should =~ matching_namespaced_keys end end context 'without :match supplied' do it 'should retrieve the proper keys' do _, result = @namespaced.scan(0, :count => 1000) result.should =~ namespaced_keys end end end if Redis.current.respond_to?(:scan) context '#scan_each' do context 'when :match supplied' do context 'when given a block' do it 'should yield unnamespaced' do results = [] @namespaced.scan_each(:match => 'zeta:*', :count => 1000) {|k| results << k } results.should =~ matching_namespaced_keys end end context 'without a block' do it 'should return an Enumerator that un-namespaces' do enum = @namespaced.scan_each(:match => 'zeta:*', :count => 1000) enum.to_a.should =~ matching_namespaced_keys end end end context 'without :match supplied' do context 'when given a block' do it 'should yield unnamespaced' do results = [] @namespaced.scan_each(:count => 1000){ |k| results << k } results.should =~ namespaced_keys end end context 'without a block' do it 'should return an Enumerator that un-namespaces' do enum = @namespaced.scan_each(:count => 1000) enum.to_a.should =~ namespaced_keys end end end end if Redis.current.respond_to?(:scan_each) end context 'hash scan methods' do before(:each) do @redis.mapped_hmset('hsh', {'zeta:wrong:one' => 'WRONG', 'wrong:two' => 'WRONG'}) @redis.mapped_hmset('ns:hsh', hash) end let(:hash) do {'zeta:one' => 'OK', 'zeta:two' => 'OK', 'three' => 'OKAY'} end let(:hash_matching_subset) do # select is not consistent from 1.8.7 -> 1.9.2 :( hash.reject {|k,v| !k[/\Azeta:/] } end context '#hscan' do context 'when supplied :match' do it 'should retrieve the proper keys' do _, results = @namespaced.hscan('hsh', 0, :match => 'zeta:*') results.should =~ hash_matching_subset.to_a end end context 'without :match supplied' do it 'should retrieve all hash keys' do _, results = @namespaced.hscan('hsh', 0) results.should =~ @redis.hgetall('ns:hsh').to_a end end end if Redis.current.respond_to?(:hscan) context '#hscan_each' do context 'when :match supplied' do context 'when given a block' do it 'should yield the correct hash keys unchanged' do results = [] @namespaced.hscan_each('hsh', :match => 'zeta:*', :count => 1000) { |kv| results << kv} results.should =~ hash_matching_subset.to_a end end context 'without a block' do it 'should return an Enumerator that yields the correct hash keys unchanged' do enum = @namespaced.hscan_each('hsh', :match => 'zeta:*', :count => 1000) enum.to_a.should =~ hash_matching_subset.to_a end end end context 'without :match supplied' do context 'when given a block' do it 'should yield all hash keys unchanged' do results = [] @namespaced.hscan_each('hsh', :count => 1000){ |k| results << k } results.should =~ hash.to_a end end context 'without a block' do it 'should return an Enumerator that yields all keys unchanged' do enum = @namespaced.hscan_each('hsh', :count => 1000) enum.to_a.should =~ hash.to_a end end end end if Redis.current.respond_to?(:hscan_each) end context 'set scan methods' do before(:each) do set.each { |elem| @namespaced.sadd('set', elem) } @redis.sadd('set', 'WRONG') end let(:set) do %w(zeta:one zeta:two three) end let(:matching_subset) do set.select { |e| e[/\Azeta:/] } end context '#sscan' do context 'when supplied :match' do it 'should retrieve the matching set members from the proper set' do _, results = @namespaced.sscan('set', 0, :match => 'zeta:*', :count => 1000) results.should =~ matching_subset end end context 'without :match supplied' do it 'should retrieve all set members from the proper set' do _, results = @namespaced.sscan('set', 0, :count => 1000) results.should =~ set end end end if Redis.current.respond_to?(:sscan) context '#sscan_each' do context 'when :match supplied' do context 'when given a block' do it 'should yield the correct hset elements unchanged' do results = [] @namespaced.sscan_each('set', :match => 'zeta:*', :count => 1000) { |kv| results << kv} results.should =~ matching_subset end end context 'without a block' do it 'should return an Enumerator that yields the correct set elements unchanged' do enum = @namespaced.sscan_each('set', :match => 'zeta:*', :count => 1000) enum.to_a.should =~ matching_subset end end end context 'without :match supplied' do context 'when given a block' do it 'should yield all set elements unchanged' do results = [] @namespaced.sscan_each('set', :count => 1000){ |k| results << k } results.should =~ set end end context 'without a block' do it 'should return an Enumerator that yields all set elements unchanged' do enum = @namespaced.sscan_each('set', :count => 1000) enum.to_a.should =~ set end end end end if Redis.current.respond_to?(:sscan_each) end context 'zset scan methods' do before(:each) do hash.each {|member, score| @namespaced.zadd('zset', score, member)} @redis.zadd('zset', 123.45, 'WRONG') end let(:hash) do {'zeta:one' => 1, 'zeta:two' => 2, 'three' => 3} end let(:hash_matching_subset) do # select is not consistent from 1.8.7 -> 1.9.2 :( hash.reject {|k,v| !k[/\Azeta:/] } end context '#zscan' do context 'when supplied :match' do it 'should retrieve the matching set elements and their scores' do results = [] @namespaced.zscan_each('zset', :match => 'zeta:*', :count => 1000) { |ms| results << ms } results.should =~ hash_matching_subset.to_a end end context 'without :match supplied' do it 'should retrieve all set elements and their scores' do results = [] @namespaced.zscan_each('zset', :count => 1000) { |ms| results << ms } results.should =~ hash.to_a end end end if Redis.current.respond_to?(:zscan) context '#zscan_each' do context 'when :match supplied' do context 'when given a block' do it 'should yield the correct set elements and scores unchanged' do results = [] @namespaced.zscan_each('zset', :match => 'zeta:*', :count => 1000) { |ms| results << ms} results.should =~ hash_matching_subset.to_a end end context 'without a block' do it 'should return an Enumerator that yields the correct set elements and scoresunchanged' do enum = @namespaced.zscan_each('zset', :match => 'zeta:*', :count => 1000) enum.to_a.should =~ hash_matching_subset.to_a end end end context 'without :match supplied' do context 'when given a block' do it 'should yield all set elements and scores unchanged' do results = [] @namespaced.zscan_each('zset', :count => 1000){ |ms| results << ms } results.should =~ hash.to_a end end context 'without a block' do it 'should return an Enumerator that yields all set elements and scores unchanged' do enum = @namespaced.zscan_each('zset', :count => 1000) enum.to_a.should =~ hash.to_a end end end end if Redis.current.respond_to?(:zscan_each) end end end # Only test aliasing functionality for Redis clients that support aliases. unless Redis::Namespace::ALIASES.empty? it "should support command aliases (delete)" do @namespaced.delete('foo') @redis.should_not have_key('ns:foo') end it "should support command aliases (set_add)" do @namespaced.set_add('bar', 'quux') @namespaced.smembers('bar').should include('quux') end it "should support command aliases (push_head)" do @namespaced.push_head('bar', 'quux') @redis.llen('ns:bar').should eq(1) end it "should support command aliases (zset_add)" do @namespaced.zset_add('bar', 1, 'quux') @redis.zcard('ns:bar').should eq(1) end end end