fakeredis-0.8.0/0000755000004100000410000000000013627672425013522 5ustar www-datawww-datafakeredis-0.8.0/.travis.yml0000644000004100000410000000044013627672425015631 0ustar www-datawww-datalanguage: ruby cache: bundler # Use the faster container based infrastructure # http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ sudo: false rvm: - 2.4 - 2.5 - 2.6 - ruby-head - jruby - rbx-2 matrix: allow_failures: - rvm: rbx-2 fakeredis-0.8.0/README.md0000644000004100000410000000471713627672425015012 0ustar www-datawww-data# FakeRedis [![Build Status](https://secure.travis-ci.org/guilleiguaran/fakeredis.png)](http://travis-ci.org/guilleiguaran/fakeredis) This a fake implementation of redis-rb for machines without Redis or test environments ## Installation Install the gem: gem install fakeredis Add it to your Gemfile: gem "fakeredis" ## Versions FakeRedis currently supports redis-rb v3 or later, if you are using redis-rb v2.2 install the version 0.3.x: gem install fakeredis -v "~> 0.3.0" or use the branch 0-3-x on your Gemfile: gem "fakeredis", :git => "git://github.com/guilleiguaran/fakeredis.git", :branch => "0-3-x" ## Usage You can use FakeRedis without any changes: ``` require "fakeredis" redis = Redis.new >> redis.set "foo", "bar" => "OK" >> redis.get "foo" => "bar" ``` Read [redis-rb](https://github.com/redis/redis-rb) documentation and [Redis](http://redis.io) homepage for more info about commands ## Usage with RSpec Require this either in your Gemfile or in RSpec's support scripts. So either: ```ruby # Gemfile group :test do gem "rspec" gem "fakeredis", :require => "fakeredis/rspec" end ``` Or: ```ruby # spec/support/fakeredis.rb require 'fakeredis/rspec' ``` ## Usage with Minitest Require this either in your Gemfile or in Minitest's support scripts. So either: ```ruby # Gemfile group :test do gem "minitest" gem "fakeredis", :require => "fakeredis/minitest" end ``` Or: ```ruby # test/test_helper.rb (or test/minitest_config.rb) require 'fakeredis/minitest' ``` ## Acknowledgements Thanks to [all contributors](https://github.com/guilleiguaran/fakeredis/graphs/contributors), specially to [Caius Durling](https://github.com/caius) the most active one. ## Contributing to FakeRedis * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it * Fork the project * Start a feature/bugfix branch * Commit and push until you are happy with your contribution * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. ## Copyright Copyright (c) 2011-2018 Guillermo Iguaran. See LICENSE for further details. fakeredis-0.8.0/spec/0000755000004100000410000000000013627672425014454 5ustar www-datawww-datafakeredis-0.8.0/spec/strings_spec.rb0000644000004100000410000002500113627672425017502 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' module FakeRedis describe "StringsMethods" do before(:each) do @client = Redis.new end it "should append a value to key" do @client.set("key1", "Hello") @client.append("key1", " World") expect(@client.get("key1")).to eq("Hello World") end it "should decrement the integer value of a key by one" do @client.set("counter", "1") @client.decr("counter") expect(@client.get("counter")).to eq("0") end it "should decrement the integer value of a key by the given number" do @client.set("counter", "10") @client.decrby("counter", "5") expect(@client.get("counter")).to eq("5") end it "should get the value of a key" do expect(@client.get("key2")).to eq(nil) end it "should returns the bit value at offset in the string value stored at key" do @client.set("key1", "a") expect(@client.getbit("key1", 1)).to eq(1) expect(@client.getbit("key1", 2)).to eq(1) expect(@client.getbit("key1", 3)).to eq(0) expect(@client.getbit("key1", 4)).to eq(0) expect(@client.getbit("key1", 5)).to eq(0) expect(@client.getbit("key1", 6)).to eq(0) expect(@client.getbit("key1", 7)).to eq(1) end it "should allow direct bit manipulation even if the string isn't set" do @client.setbit("key1", 10, 1) expect(@client.getbit("key1", 10)).to eq(1) end context 'when a bit is previously set to 0' do before { @client.setbit("key1", 10, 0) } it 'setting it to 1 returns 0' do expect(@client.setbit("key1", 10, 1)).to eql 0 end it 'setting it to 0 returns 0' do expect(@client.setbit("key1", 10, 0)).to eql 0 end end context 'when a bit is previously set to 1' do before { @client.setbit("key1", 10, 1) } it 'setting it to 0 returns 1' do expect(@client.setbit("key1", 10, 0)).to eql 1 end it 'setting it to 1 returns 1' do expect(@client.setbit("key1", 10, 1)).to eql 1 end end it "should get a substring of the string stored at a key" do @client.set("key1", "This a message") expect(@client.getrange("key1", 0, 3)).to eq("This") expect(@client.substr("key1", 0, 3)).to eq("This") end it "should set the string value of a key and return its old value" do @client.set("key1","value1") expect(@client.getset("key1", "value2")).to eq("value1") expect(@client.get("key1")).to eq("value2") end it "should return nil for #getset if the key does not exist when setting" do expect(@client.getset("key1", "value1")).to eq(nil) expect(@client.get("key1")).to eq("value1") end it "should increment the integer value of a key by one" do @client.set("counter", "1") expect(@client.incr("counter")).to eq(2) expect(@client.get("counter")).to eq("2") end it "should not change the expire value of the key during incr" do @client.set("counter", "1") expect(@client.expire("counter", 600)).to be true expect(@client.ttl("counter")).to eq(600) expect(@client.incr("counter")).to eq(2) expect(@client.ttl("counter")).to eq(600) end it "should decrement the integer value of a key by one" do @client.set("counter", "1") expect(@client.decr("counter")).to eq(0) expect(@client.get("counter")).to eq("0") end it "should not change the expire value of the key during decr" do @client.set("counter", "2") expect(@client.expire("counter", 600)).to be true expect(@client.ttl("counter")).to eq(600) expect(@client.decr("counter")).to eq(1) expect(@client.ttl("counter")).to eq(600) end it "should increment the integer value of a key by the given number" do @client.set("counter", "10") expect(@client.incrby("counter", "5")).to eq(15) expect(@client.incrby("counter", 2)).to eq(17) expect(@client.get("counter")).to eq("17") end it "should increment the float value of a key by the given number" do @client.set("counter", 10.0) expect(@client.incrbyfloat("counter", 2.1)).to eq(12.1) expect(@client.get("counter")).to eq("12.1") end it "should not change the expire value of the key during incrby" do @client.set("counter", "1") expect(@client.expire("counter", 600)).to be true expect(@client.ttl("counter")).to eq(600) expect(@client.incrby("counter", "5")).to eq(6) expect(@client.ttl("counter")).to eq(600) end it "should decrement the integer value of a key by the given number" do @client.set("counter", "10") expect(@client.decrby("counter", "5")).to eq(5) expect(@client.decrby("counter", 2)).to eq(3) expect(@client.get("counter")).to eq("3") end it "should not change the expire value of the key during decrby" do @client.set("counter", "8") expect(@client.expire("counter", 600)).to be true expect(@client.ttl("counter")).to eq(600) expect(@client.decrby("counter", "3")).to eq(5) expect(@client.ttl("counter")).to eq(600) end it "should get the values of all the given keys" do @client.set("key1", "value1") @client.set("key2", "value2") @client.set("key3", "value3") expect(@client.mget("key1", "key2", "key3")).to eq(["value1", "value2", "value3"]) expect(@client.mget(["key1", "key2", "key3"])).to eq(["value1", "value2", "value3"]) end it "returns nil for non existent keys" do @client.set("key1", "value1") @client.set("key3", "value3") expect(@client.mget("key1", "key2", "key3", "key4")).to eq(["value1", nil, "value3", nil]) expect(@client.mget(["key1", "key2", "key3", "key4"])).to eq(["value1", nil, "value3", nil]) end it 'raises an argument error when not passed any fields' do @client.set("key3", "value3") expect { @client.mget }.to raise_error(Redis::CommandError) end it "should set multiple keys to multiple values" do @client.mset(:key1, "value1", :key2, "value2") expect(@client.get("key1")).to eq("value1") expect(@client.get("key2")).to eq("value2") end it "should raise error if command arguments count is wrong" do expect { @client.mset }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'mset' command") expect { @client.mset(:key1) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'mset' command") expect { @client.mset(:key1, "value", :key2) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for MSET") expect(@client.get("key1")).to be_nil expect(@client.get("key2")).to be_nil end it "should set multiple keys to multiple values, only if none of the keys exist" do expect(@client.msetnx(:key1, "value1", :key2, "value2")).to eq(true) expect(@client.msetnx(:key1, "value3", :key2, "value4")).to eq(false) expect(@client.get("key1")).to eq("value1") expect(@client.get("key2")).to eq("value2") end it "should set multiple keys to multiple values with a hash" do @client.mapped_mset(:key1 => "value1", :key2 => "value2") expect(@client.get("key1")).to eq("value1") expect(@client.get("key2")).to eq("value2") end it "should set multiple keys to multiple values with a hash, only if none of the keys exist" do expect(@client.mapped_msetnx(:key1 => "value1", :key2 => "value2")).to eq(true) expect(@client.mapped_msetnx(:key1 => "value3", :key2 => "value4")).to eq(false) expect(@client.get("key1")).to eq("value1") expect(@client.get("key2")).to eq("value2") end it "should set the string value of a key" do @client.set("key1", "1") expect(@client.get("key1")).to eq("1") end it "should sets or clears the bit at offset in the string value stored at key" do @client.set("key1", "abc") @client.setbit("key1", 11, 1) expect(@client.get("key1")).to eq("arc") end it "should set the value and expiration of a key" do @client.setex("key1", 30, "value1") expect(@client.get("key1")).to eq("value1") expect(@client.ttl("key1")).to eq(30) end it "should set the value of a key, only if the key does not exist" do expect(@client.setnx("key1", "test value")).to eq(true) expect(@client.setnx("key1", "new value")).to eq(false) @client.setnx("key2", "another value") expect(@client.get("key1")).to eq("test value") expect(@client.get("key2")).to eq("another value") end it "should overwrite part of a string at key starting at the specified offset" do @client.set("key1", "Hello World") @client.setrange("key1", 6, "Redis") expect(@client.get("key1")).to eq("Hello Redis") end it "should get the length of the value stored in a key" do @client.set("key1", "abc") expect(@client.strlen("key1")).to eq(3) end it "should return 0 bits when there's no key" do expect(@client.bitcount("key1")).to eq(0) end it "should count the number of bits of a string" do @client.set("key1", "foobar") expect(@client.bitcount("key1")).to eq(26) end it "should count correctly with UTF-8 strings" do @client.set("key1", '判') expect(@client.bitcount("key1")).to eq(10) end it "should count the number of bits of a string given a range" do @client.set("key1", "foobar") expect(@client.bitcount("key1", 0, 0)).to eq(4) expect(@client.bitcount("key1", 1, 1)).to eq(6) expect(@client.bitcount("key1", 0, 1)).to eq(10) end describe "#bitpos" do it "should return -1 when there's no key" do expect(@client.bitpos("key", 0)).to eq(-1) end it "should return -1 for empty key" do @client.set("key", "") expect(@client.bitpos("key", 0)).to eq(-1) end it "should return position of the bit in a string" do @client.set("key", "foobar") # 01100110 01101111 01101111 expect(@client.bitpos("key", 1)).to eq(1) end it "should return position of the bit correctly with UTF-8 strings" do @client.set("key", "判") # 11100101 10001000 10100100 expect(@client.bitpos("key", 0)).to eq(3) end it "should return position of the bit in a string given a range" do @client.set("key", "foobar") expect(@client.bitpos("key", 1, 0)).to eq(1) expect(@client.bitpos("key", 1, 1, 2)).to eq(9) expect(@client.bitpos("key", 0, 1, -1)).to eq(8) end end end end fakeredis-0.8.0/spec/hyper_log_logs_spec.rb0000644000004100000410000000313713627672425021033 0ustar www-datawww-datarequire "spec_helper" module FakeRedis describe "HyperLogLogsMethods" do let(:redis) { Redis.new } it "should add item to hyperloglog" do expect(redis.pfadd("hll", "val")).to eq(true) expect(redis.pfcount("hll")).to eq(1) end it "should not add duplicated item to hyperloglog" do redis.pfadd("hll", "val") expect(redis.pfadd("hll", "val")).to eq(false) expect(redis.pfcount("hll")).to eq(1) end it "should not add multiple items to hyperloglog" do expect(redis.pfadd("hll", ["val1", "val2"])).to eq(true) expect(redis.pfcount("hll")).to eq(2) end it "should return zero as cardinality for nonexistent key" do expect(redis.pfcount("nonexistent")).to eq(0) end it "should return cardinality of union of hyperloglogs" do redis.pfadd("hll1", ["val1", "val2"]) redis.pfadd("hll2", ["val2", "val3"]) expect(redis.pfcount("hll1", "hll2")).to eq(3) end it "should error if an empty list of keys is given" do expect { redis.pfcount([]) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'pfcount' command") end it "should merge hyperloglogs" do redis.pfadd("hll1", ["val1", "val2"]) redis.pfadd("hll2", ["val2", "val3"]) expect(redis.pfmerge("hll3", "hll1", "hll2")).to eq(true) expect(redis.pfcount("hll3")).to eq(3) end it "should merge nonexistent hyperloglogs with others" do redis.pfadd("hll1", "val") expect(redis.pfmerge("hll3", "hll1", "nonexistent")).to eq(true) expect(redis.pfcount("hll3")).to eq(1) end end end fakeredis-0.8.0/spec/transactions_spec.rb0000644000004100000410000000543713627672425020534 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "TransactionsMethods" do before(:all) do @client = Redis.new end before(:each) do @client.discard rescue nil end context '#multi' do it "should respond with 'OK'" do expect(@client.multi).to eq('OK') end it "should forbid nesting" do @client.multi expect{@client.multi}.to raise_error(Redis::CommandError) end it "should mark the start of a transaction block" do transaction = @client.multi do |multi| multi.set("key1", "1") multi.set("key2", "2") multi.expire("key1", 123) multi.mget("key1", "key2") end expect(transaction).to eq(["OK", "OK", true, ["1", "2"]]) end end context '#discard' do it "should respond with 'OK' after #multi" do @client.multi expect(@client.discard).to eq('OK') end it "can't be run outside of #multi/#exec" do expect{@client.discard}.to raise_error(Redis::CommandError) end end context '#exec' do it "can't be run outside of #multi" do expect{@client.exec}.to raise_error(Redis::CommandError) end end context 'saving up commands for later' do before(:each) do @client.multi @string = 'fake-redis-test:string' @list = 'fake-redis-test:list' end it "makes commands respond with 'QUEUED'" do expect(@client.set(@string, 'string')).to eq('QUEUED') expect(@client.lpush(@list, 'list')).to eq('QUEUED') end it "gives you the commands' responses when you call #exec" do @client.set(@string, 'string') @client.lpush(@list, 'list') @client.lpush(@list, 'list') expect(@client.exec).to eq(['OK', 1, 2]) end it "does not raise exceptions, but rather puts them in #exec's response" do @client.set(@string, 'string') @client.lpush(@string, 'oops!') @client.lpush(@list, 'list') responses = @client.exec expect(responses[0]).to eq('OK') expect(responses[1]).to be_a(RuntimeError) expect(responses[2]).to eq(1) end end context 'executing hash commands in a block' do it "returns true if the nested hash command succeeds" do responses = @client.multi { |multi| multi.hset('hash', 'key', 'value') } expect(responses[0]).to eq(true) end end context 'executing set commands in a block' do it "returns commands' responses for nested commands" do @client.sadd('set', 'member1') responses = @client.multi do |multi| multi.sadd('set', 'member1') multi.sadd('set', 'member2') end expect(responses).to eq([false, true]) end end end end fakeredis-0.8.0/spec/fakeredis_spec.rb0000644000004100000410000000320213627672425017745 0ustar www-datawww-datarequire 'spec_helper' describe FakeRedis do after { described_class.disable } describe '.enable' do it 'in memory connection' do described_class.enable expect(described_class.enabled?).to be_truthy end end describe '.disable' do before { described_class.enable } it 'in memory connection' do described_class.disable expect(described_class.enabled?).to be_falsy end end describe '.disabling' do context 'FakeRedis is enabled' do before { described_class.enable } it 'in memory connection' do described_class.disabling do expect(described_class.enabled?).to be_falsy end expect(described_class.enabled?).to be_truthy end end context 'FakeRedis is disabled' do before { described_class.disable } it 'in memory connection' do described_class.disabling do expect(described_class.enabled?).to be_falsy end expect(described_class.enabled?).to be_falsy end end end describe '.enabling' do context 'FakeRedis is enabled' do before { described_class.enable } it 'in memory connection' do described_class.enabling do expect(described_class.enabled?).to be_truthy end expect(described_class.enabled?).to be_truthy end end context 'FakeRedis is disabled' do before { described_class.disable } it 'in memory connection' do described_class.enabling do expect(described_class.enabled?).to be_truthy end expect(described_class.enabled?).to be_falsy end end end end fakeredis-0.8.0/spec/geo_set_spec.rb0000644000004100000410000001324613627672425017446 0ustar www-datawww-datarequire "spec_helper" module FakeRedis describe "GeoMethods" do before do @client = Redis.new end describe "#geoadd" do it "should raise when not enough arguments" do expect { @client.geoadd("Sicily", []) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'geoadd' command") expect { @client.geoadd("Sicily", [13.361389, 38.115556]) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'geoadd' command") end it "should add items to the set" do added_items_count = add_sicily expect(added_items_count).to eq(2) end it "should update existing items" do @client.geoadd("Sicily", 13.361389, 38.115556, "Palermo") added_items_count = @client.geoadd("Sicily", 13, 39, "Palermo", 15.087269, 37.502669, "Catania") expect(added_items_count).to eq(1) end end describe "#geodist" do before do add_sicily end it "should return destination between two elements" do distance_in_meters = @client.geodist("Sicily", "Palermo", "Catania") expect(distance_in_meters).to eq("166412.6051") distance_in_feet = @client.geodist("Sicily", "Palermo", "Catania", "ft") expect(distance_in_feet).to eq("545973.1137") end it "should raise for unknown unit name" do expect { @client.geodist("Sicily", "Palermo", "Catania", "unknown") }.to raise_error(Redis::CommandError, "ERR unsupported unit provided. please use m, km, ft, mi") end it "should return nil when element is missing" do expect(@client.geodist("Sicily", "Palermo", "Rome")).to be_nil end end describe "#geohash" do before do add_sicily end it "should raise when not enough arguments" do expect { @client.geohash("Sicily", []) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'geohash' command") end it "should return geohashes" do geohash = @client.geohash("Sicily", "Palermo") expect(geohash).to eq(["sqc8b49rny"]) geohashes = @client.geohash("Sicily", ["Palermo", "Catania"]) expect(geohashes).to eq(["sqc8b49rny", "sqdtr74hyu"]) end it "should return nils for nonexistent elements" do geohashes = @client.geohash("Sicily", ["Palermo", "Rome"]) expect(geohashes).to eq(["sqc8b49rny", nil]) end end describe "#geopos" do it "should return positions (longitude, latitude) for elements" do add_sicily position = @client.geopos("Sicily", "Catania") expect(position).to eq([["15.087269", "37.502669"]]) positions = @client.geopos("Sicily", ["Palermo", "Catania"]) expect(positions).to eq([["13.361389", "38.115556"], ["15.087269", "37.502669"]]) end it "should return nil for nonexistent elements" do expect(@client.geopos("nonexistent", "nonexistent2")).to be_nil add_sicily position = @client.geopos("Sicily", "Rome") expect(position).to eq([nil]) positions = @client.geopos("Sicily", ["Rome", "Catania"]) expect(positions).to eq([nil, ["15.087269", "37.502669"]]) end end describe "#georadius" do before do add_sicily end it "should return members within specified radius" do nearest_cities = @client.georadius("Sicily", 15, 37, 100, "km") expect(nearest_cities).to eq(["Catania"]) end it "should sort returned members" do nearest_cities = @client.georadius("Sicily", 15, 37, 200, "km", sort: "asc") expect(nearest_cities).to eq(["Catania", "Palermo"]) farthest_cities = @client.georadius("Sicily", 15, 37, 200, "km", sort: "desc") expect(farthest_cities).to eq(["Palermo", "Catania"]) end it "should return specified count of members" do city = @client.georadius("Sicily", 15, 37, 200, "km", sort: "asc", count: 1) expect(city).to eq(["Catania"]) end it "should include additional info for members" do cities = @client.georadius("Sicily", 15, 37, 200, "km", "WITHDIST") expect(cities).to eq([["Palermo", "190.6009"], ["Catania", "56.4883"]]) cities = @client.georadius("Sicily", 15, 37, 200, "km", "WITHCOORD") expect(cities).to eq [["Palermo", ["13.361389", "38.115556"]], ["Catania", ["15.087269", "37.502669"]]] cities = @client.georadius("Sicily", 15, 37, 200, "km", "WITHDIST", "WITHCOORD") expect(cities).to eq( [["Palermo", "190.6009", ["13.361389", "38.115556"]], ["Catania", "56.4883", ["15.087269", "37.502669"]]] ) end end describe "#georadiusbymember" do before do add_sicily end it "should sort returned members" do nearest_cities = @client.georadiusbymember("Sicily", "Catania", 200, "km", sort: "asc") expect(nearest_cities).to eq(["Catania", "Palermo"]) farthest_cities = @client.georadiusbymember("Sicily", "Catania", 200, "km", sort: "desc") expect(farthest_cities).to eq(["Palermo", "Catania"]) end it "should limit number of returned members" do city = @client.georadiusbymember("Sicily", "Catania", 100, "km", count: 1) expect(city).to eq(["Catania"]) end it "should include extra info if requested" do city = @client.georadiusbymember("Sicily", "Catania", 200, "km", sort: :desc, options: :WITHDIST, count: 1) expect(city).to eq([["Palermo", "166.4126"]]) end end private def add_sicily @client.geoadd("Sicily", 13.361389, 38.115556, "Palermo", 15.087269, 37.502669, "Catania") end end end fakeredis-0.8.0/spec/spec_helper_live_redis.rb0000644000004100000410000000033413627672425021477 0ustar www-datawww-datarequire "spec_helper" RSpec.configure do |config| config.before(:each) do # Disable so we test against actual redis FakeRedis.disable Redis.new.flushall end end def fakeredis? FakeRedis.enabled? end fakeredis-0.8.0/spec/hashes_spec.rb0000644000004100000410000002332613627672425017274 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "HashesMethods" do before(:each) do @client = Redis.new end it "should delete a hash field" do @client.hset("key1", "k1", "val1") @client.hset("key1", "k2", "val2") expect(@client.hdel("key1", "k1")).to be(1) expect(@client.hget("key1", "k1")).to be_nil expect(@client.hget("key1", "k2")).to eq("val2") end it "should delete array of fields" do @client.hset("key1", "k1", "val1") @client.hset("key1", "k2", "val2") @client.hset("key1", "k3", "val3") expect(@client.hdel("key1", ["k1", "k2"])).to be(2) expect(@client.hget("key1", "k1")).to be_nil expect(@client.hget("key1", "k2")).to be_nil expect(@client.hget("key1", "k3")).to eq("val3") end it "should remove a hash with no keys left" do @client.hset("key1", "k1", "val1") @client.hset("key1", "k2", "val2") expect(@client.hdel("key1", "k1")).to be(1) expect(@client.hdel("key1", "k2")).to be(1) expect(@client.exists("key1")).to eq(false) end it "should convert key to a string for hset" do m = double("key") allow(m).to receive(:to_s).and_return("foo") @client.hset("key1", m, "bar") expect(@client.hget("key1", "foo")).to eq("bar") end it "should convert key to a string for hget" do m = double("key") allow(m).to receive(:to_s).and_return("foo") @client.hset("key1", "foo", "bar") expect(@client.hget("key1", m)).to eq("bar") end it "should determine if a hash field exists" do @client.hset("key1", "index", "value") expect(@client.hexists("key1", "index")).to be true expect(@client.hexists("key2", "i2")).to be false end it "should get the value of a hash field" do @client.hset("key1", "index", "value") expect(@client.hget("key1", "index")).to eq("value") end it "should get all the fields and values in a hash" do @client.hset("key1", "i1", "val1") @client.hset("key1", "i2", "val2") expect(@client.hgetall("key1")).to eq({"i1" => "val1", "i2" => "val2"}) end it "should increment the integer value of a hash field by the given number" do @client.hset("key1", "cont1", "5") expect(@client.hincrby("key1", "cont1", "5")).to eq(10) expect(@client.hget("key1", "cont1")).to eq("10") end it "should increment non existing hash keys" do expect(@client.hget("key1", "cont2")).to be_nil expect(@client.hincrby("key1", "cont2", "5")).to eq(5) end it "should increment the float value of a hash field by the given float" do @client.hset("key1", "cont1", 5.0) expect(@client.hincrbyfloat("key1", "cont1", 4.1)).to eq(9.1) expect(@client.hget("key1", "cont1")).to eq("9.1") end it "should increment non existing hash keys" do expect(@client.hget("key1", "cont2")).to be_nil expect(@client.hincrbyfloat("key1", "cont2", 5.5)).to eq(5.5) end it "should get all the fields in a hash" do @client.hset("key1", "i1", "val1") @client.hset("key1", "i2", "val2") expect(@client.hkeys("key1")).to match_array(["i1", "i2"]) expect(@client.hkeys("key2")).to eq([]) end it "should get the number of fields in a hash" do @client.hset("key1", "i1", "val1") @client.hset("key1", "i2", "val2") expect(@client.hlen("key1")).to eq(2) end it "should get the string length of the value associated with field in a hash" do @client.hset("key1", "i1", "val1") expect(@client.hstrlen("key1", "i1")).to eq(4) expect(@client.hstrlen("key1", "nonexistent")).to eq(0) expect(@client.hstrlen("nonexistent", "field")).to eq(0) end it "should get the values of all the given hash fields" do @client.hset("key1", "i1", "val1") @client.hset("key1", "i2", "val2") expect(@client.hmget("key1", "i1", "i2", "i3")).to match_array(["val1", "val2", nil]) expect(@client.hmget("key1", ["i1", "i2", "i3"])).to match_array(["val1", "val2", nil]) expect(@client.hmget("key2", "i1", "i2")).to eq([nil, nil]) end it "should throw an argument error when you don't ask for any keys" do expect { @client.hmget("key1") }.to raise_error(Redis::CommandError) end it "should throw an argument error when the list of keys you ask for is empty" do expect { @client.hmget("key1", []) }.to raise_error(Redis::CommandError) end it "should reject an empty list of values" do expect { @client.hmset("key") }.to raise_error(Redis::CommandError) expect(@client.exists("key")).to be false end it "rejects an insert with a key but no value" do expect { @client.hmset("key", 'foo') }.to raise_error(Redis::CommandError) expect { @client.hmset("key", 'foo', 3, 'bar') }.to raise_error(Redis::CommandError) expect(@client.exists("key")).to be false end it "should reject the wrong number of arguments" do expect { @client.hmset("hash", "foo1", "bar1", "foo2", "bar2", "foo3") }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for HMSET") end it "should return OK on success" do expect(@client.hmset("key", "k1", "value1")).to eq("OK") end it "should set multiple hash fields to multiple values" do @client.hmset("key", "k1", "value1", "k2", "value2") expect(@client.hget("key", "k1")).to eq("value1") expect(@client.hget("key", "k2")).to eq("value2") end it "should set multiple hash fields from a ruby hash to multiple values" do @client.mapped_hmset("foo", :k1 => "value1", :k2 => "value2") expect(@client.hget("foo", "k1")).to eq("value1") expect(@client.hget("foo", "k2")).to eq("value2") end it "should set the string value of a hash field" do expect(@client.hset("key1", "k1", "val1")).to eq(true) expect(@client.hset("key1", "k1", "val1")).to eq(false) expect(@client.hget("key1", "k1")).to eq("val1") end it "should set the value of a hash field, only if the field does not exist" do @client.hset("key1", "k1", "val1") expect(@client.hsetnx("key1", "k1", "value")).to eq(false) expect(@client.hsetnx("key1", "k2", "val2")).to eq(true) expect(@client.hsetnx("key1", :k1, "value")).to eq(false) expect(@client.hsetnx("key1", :k3, "val3")).to eq(true) expect(@client.hget("key1", "k1")).to eq("val1") expect(@client.hget("key1", "k2")).to eq("val2") expect(@client.hget("key1", "k3")).to eq("val3") end it "should get all the values in a hash" do @client.hset("key1", "k1", "val1") @client.hset("key1", "k2", "val2") expect(@client.hvals("key1")).to match_array(["val1", "val2"]) end it "should accept a list of array pairs as arguments and not throw an invalid argument number error" do @client.hmset("key1", [:k1, "val1"], [:k2, "val2"], [:k3, "val3"]) expect(@client.hget("key1", :k1)).to eq("val1") expect(@client.hget("key1", :k2)).to eq("val2") expect(@client.hget("key1", :k3)).to eq("val3") end it "should reject a list of arrays that contain an invalid number of arguments" do expect { @client.hmset("key1", [:k1, "val1"], [:k2, "val2", "bogus val"]) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for HMSET") end it "should convert a integer field name to string for hdel" do @client.hset("key1", "1", 1) expect(@client.hdel("key1", 1)).to be(1) end it "should convert a integer field name to string for hexists" do @client.hset("key1", "1", 1) expect(@client.hexists("key1", 1)).to be true end it "should convert a integer field name to string for hincrby" do @client.hset("key1", 1, 0) expect(@client.hincrby("key1", 1, 1)).to be(1) end describe "#hscan" do it 'with no arguments and few items, returns all items' do @client.hmset('hash', 'name', 'Jack', 'age', '33') result = @client.hscan('hash', 0) expect(result[0]).to eq('0') expect(result[1]).to eq([['name', 'Jack'], ['age', '33']]) end it 'with a count should return that number of members or more' do @client.hmset('hash', 'a', '1', 'b', '2', 'c', '3', 'd', '4', 'e', '5', 'f', '6', 'g', '7' ) result = @client.hscan('hash', 0, count: 3) expect(result[0]).to eq('3') expect(result[1]).to eq([ ['a', '1'], ['b', '2'], ['c', '3'], ]) end it 'returns items starting at the provided cursor' do @client.hmset('hash', 'a', '1', 'b', '2', 'c', '3', 'd', '4', 'e', '5', 'f', '6', 'g', '7' ) result = @client.hscan('hash', 2, count: 3) expect(result[0]).to eq('5') expect(result[1]).to eq([ ['c', '3'], ['d', '4'], ['e', '5'] ]) end it 'with match, returns items matching the given pattern' do @client.hmset('hash', 'aa', '1', 'b', '2', 'cc', '3', 'd', '4', 'ee', '5', 'f', '6', 'gg', '7' ) result = @client.hscan('hash', 2, count: 3, match: '??') expect(result[0]).to eq('5') expect(result[1]).to eq([ ['cc', '3'], ['ee', '5'] ]) end it 'returns an empty result if the key is not found' do result = @client.hscan('hash', 0) expect(result[0]).to eq('0') expect(result[1]).to eq([]) end end end end fakeredis-0.8.0/spec/subscription_spec.rb0000644000004100000410000000554013627672425020543 0ustar www-datawww-datarequire 'spec_helper' require 'timeout' #Need to use this avoid blocking module FakeRedis describe "SubscriptionMethods" do before(:each) do @client = Redis.new end context "publish" do it "should add to channels" do expect(@client.publish("channel1", "val1")).to eq(0) expect(@client.publish("channel1", "val2")).to eq(0) end end context "subscribe" do it "should get all messages from a channel" do @client.publish("channel1", "val1") @client.publish("channel1", "val2") @client.publish("channel2", "val3") msgs = [] subscribe_sent = unsubscribe_sent = false Timeout.timeout(1) do @client.subscribe("channel1") do |on| on.subscribe do |channel| subscribe_sent = true expect(channel).to eq("channel1") end on.message do |channel,msg| expect(channel).to eq("channel1") msgs << msg end on.unsubscribe do unsubscribe_sent = true end end end expect(msgs).to eq(["val1", "val2"]) expect(subscribe_sent).to eq(true) expect(unsubscribe_sent).to eq(true) end it "should get all messages from multiple channels" do @client.publish("channel1", "val1") @client.publish("channel2", "val2") @client.publish("channel2", "val3") msgs = [] Timeout.timeout(1) do @client.subscribe("channel1", "channel2") do |on| on.message do |channel,msg| msgs << [channel, msg] end end end expect(msgs[0]).to eq(["channel1", "val1"]) expect(msgs[1]).to eq(["channel2", "val2"]) expect(msgs[2]).to eq(["channel2", "val3"]) end end context "unsubscribe" do end context "with patterns" do context "psubscribe" do it "should get all messages using pattern" do @client.publish("channel1", "val1") @client.publish("channel1", "val2") @client.publish("channel2", "val3") msgs = [] subscribe_sent = unsubscribe_sent = false Timeout.timeout(1) do @client.psubscribe("channel*") do |on| on.psubscribe do |channel| subscribe_sent = true end on.pmessage do |pattern,channel,msg| expect(pattern).to eq("channel*") msgs << msg end on.punsubscribe do unsubscribe_sent = true end end end expect(msgs).to eq(["val1", "val2", "val3"]) expect(subscribe_sent).to eq(true) expect(unsubscribe_sent).to eq(true) end end context "punsubscribe" do end end end end fakeredis-0.8.0/spec/sets_spec.rb0000644000004100000410000002652513627672425017003 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "SetsMethods" do before(:each) do @client = Redis.new end it "should add a member to a set" do expect(@client.sadd("key", "value")).to eq(true) expect(@client.sadd("key", "value")).to eq(false) expect(@client.smembers("key")).to eq(["value"]) end it "should raise error if command arguments count is not enough" do expect { @client.sadd("key", []) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'sadd' command") expect { @client.sinter(*[]) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'sinter' command") expect { @client.sinter([]) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'sinter' command") expect { @client.sunion(*[]) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'sunion' command") expect { @client.sunion([]) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'sunion' command") expect { @client.srem("key", []) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'srem' command") expect(@client.smembers("key")).to be_empty end it "should add multiple members to a set" do expect(@client.sadd("key", %w(value other something more))).to eq(4) expect(@client.sadd("key", %w(and additional values))).to eq(3) expect(@client.smembers("key")).to match_array(["value", "other", "something", "more", "and", "additional", "values"]) end it "should get the number of members in a set" do @client.sadd("key", "val1") @client.sadd("key", "val2") expect(@client.scard("key")).to eq(2) end it "should subtract multiple sets" do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.sadd("key1", "c") @client.sadd("key1", "d") @client.sadd("key2", "c") @client.sadd("key3", "a") @client.sadd("key3", "c") @client.sadd("key3", "e") expect(@client.sdiff("key1", "key2", "key3")).to match_array(["b", "d"]) expect(@client.sdiff("key1", ["key2", "key3"])).to match_array(["b", "d"]) end it "should subtract from a nonexistent set" do @client.sadd("key2", "a") @client.sadd("key2", "b") expect(@client.sdiff("key1", "key2")).to eq([]) expect(@client.sdiff(["key1", "key2"])).to eq([]) end it "should subtract multiple sets and store the resulting set in a key" do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.sadd("key1", "c") @client.sadd("key1", "d") @client.sadd("key2", "c") @client.sadd("key3", "a") @client.sadd("key3", "c") @client.sadd("key3", "e") @client.sdiffstore("key", "key1", "key2", "key3") @client.sdiffstore("new_key", "key1", ["key2", "key3"]) expect(@client.smembers("key")).to match_array(["b", "d"]) expect(@client.smembers("new_key")).to match_array(["b", "d"]) end it "should intersect multiple sets" do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.sadd("key1", "c") @client.sadd("key1", "d") @client.sadd("key2", "c") @client.sadd("key3", "a") @client.sadd("key3", "c") @client.sadd("key3", "e") expect(@client.sinter("key1", "key2", "key3")).to eq(["c"]) expect(@client.sinter(["key1", "key2", "key3"])).to eq(["c"]) end it "should intersect multiple sets and store the resulting set in a key" do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.sadd("key1", "c") @client.sadd("key1", "d") @client.sadd("key2", "c") @client.sadd("key3", "a") @client.sadd("key3", "c") @client.sadd("key3", "e") @client.sinterstore("key", "key1", "key2", "key3") @client.sinterstore("new_key", ["key1", "key2", "key3"]) expect(@client.smembers("key")).to eq(["c"]) expect(@client.smembers("new_key")).to eq(["c"]) end it "should determine if a given value is a member of a set" do @client.sadd("key1", "a") expect(@client.sismember("key1", "a")).to eq(true) expect(@client.sismember("key1", "b")).to eq(false) expect(@client.sismember("key2", "a")).to eq(false) end it "should get all the members in a set" do @client.sadd("key", "a") @client.sadd("key", "b") @client.sadd("key", "c") @client.sadd("key", "d") expect(@client.smembers("key")).to match_array(["a", "b", "c", "d"]) end it "should move a member from one set to another" do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.sadd("key2", "c") expect(@client.smove("key1", "key2", "a")).to eq(true) expect(@client.smove("key1", "key2", "a")).to eq(false) expect(@client.smembers("key1")).to eq(["b"]) expect(@client.smembers("key2")).to match_array(["c", "a"]) end it "should remove and return a random member from a set" do @client.sadd("key1", "a") @client.sadd("key1", "b") expect(["a", "b"].include?(@client.spop("key1"))).to be true expect(["a", "b"].include?(@client.spop("key1"))).to be true expect(@client.spop("key1")).to be_nil end it "should get a random member from a set" do @client.sadd("key1", "a") @client.sadd("key1", "b") expect(["a", "b"].include?(@client.spop("key1"))).to be true end it "should pop multiple members from a set" do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.sadd("key1", "c") vals = @client.spop("key1", 2) expect(vals.count).to eq(2) vals.each { |v| expect(["a", "b", "c"].include?(v)).to be true } new_vals = @client.spop("key1", 2) expect(new_vals.count).to eq(1) expect(["a", "b", "c"].include?(new_vals.first)).to be true expect(["a", "b", "c"]).to eq((vals + new_vals).sort) end it "should remove a member from a set" do @client.sadd("key1", "a") @client.sadd("key1", "b") expect(@client.srem("key1", "a")).to eq(true) expect(@client.srem("key1", "a")).to eq(false) expect(@client.smembers("key1")).to eq(["b"]) end it "should remove multiple members from a set" do @client.sadd("key1", "a") @client.sadd("key1", "b") expect(@client.srem("key1", [ "a", "b"])).to eq(2) expect(@client.smembers("key1")).to be_empty end it "should remove the set's key once it's empty" do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.srem("key1", "b") @client.srem("key1", "a") expect(@client.exists("key1")).to eq(false) end it "should add multiple sets" do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.sadd("key1", "c") @client.sadd("key1", "d") @client.sadd("key2", "c") @client.sadd("key3", "a") @client.sadd("key3", "c") @client.sadd("key3", "e") expect(@client.sunion("key1", "key2", "key3")).to match_array(["a", "b", "c", "d", "e"]) end it "should add multiple sets and store the resulting set in a key" do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.sadd("key1", "c") @client.sadd("key1", "d") @client.sadd("key2", "c") @client.sadd("key3", "a") @client.sadd("key3", "c") @client.sadd("key3", "e") @client.sunionstore("key", "key1", "key2", "key3") expect(@client.smembers("key")).to match_array(["a", "b", "c", "d", "e"]) end end describe 'srandmember' do before(:each) do @client = Redis.new end context 'with a set that has three elements' do before do @client.sadd("key1", "a") @client.sadd("key1", "b") @client.sadd("key1", "c") end context 'when called without the optional number parameter' do it 'is a random element from the set' do random_element = @client.srandmember("key1") expect(['a', 'b', 'c'].include?(random_element)).to be true end end context 'when called with the optional number parameter of 1' do it 'is an array of one random element from the set' do random_elements = @client.srandmember("key1", 1) expect([['a'], ['b'], ['c']].include?(random_elements)).to be true end end context 'when called with the optional number parameter of 2' do it 'is an array of two unique, random elements from the set' do random_elements = @client.srandmember("key1", 2) expect(random_elements.count).to eq(2) expect(random_elements.uniq.count).to eq(2) random_elements.all? do |element| expect(['a', 'b', 'c'].include?(element)).to be true end end end context 'when called with an optional parameter of -100' do it 'is an array of 100 random elements from the set, some of which are repeated' do random_elements = @client.srandmember("key1", -100) expect(random_elements.count).to eq(100) expect(random_elements.uniq.count).to be <= 3 random_elements.all? do |element| expect(['a', 'b', 'c'].include?(element)).to be true end end end context 'when called with an optional parameter of 100' do it 'is an array of all of the elements from the set, none of which are repeated' do random_elements = @client.srandmember("key1", 100) expect(random_elements.count).to eq(3) expect(random_elements.uniq.count).to eq(3) expect(random_elements).to match_array(['a', 'b', 'c']) end end end context 'with an empty set' do before { @client.del("key1") } it 'is nil without the extra parameter' do expect(@client.srandmember("key1")).to be_nil end it 'is an empty array with an extra parameter' do expect(@client.srandmember("key1", 1)).to eq([]) end end describe "#sscan" do it 'with no arguments and few items, returns all items' do @client.sadd('set', ['name', 'Jack', 'age', '33']) result = @client.sscan('set', 0) expect(result[0]).to eq('0') expect(result[1]).to eq(['name', 'Jack', 'age', '33']) end it 'with a count should return that number of members or more' do @client.sadd('set', ['a', '1', 'b', '2', 'c', '3', 'd', '4', 'e', '5', 'f', '6', 'g', '7']) result = @client.sscan('set', 0, count: 3) expect(result[0]).to eq('3') expect(result[1]).to eq([ 'a', '1', 'b']) end it 'returns items starting at the provided cursor' do @client.sadd('set', ['a', '1', 'b', '2', 'c', '3', 'd', '4', 'e', '5', 'f', '6', 'g', '7']) result = @client.sscan('set', 2, count: 3) expect(result[0]).to eq('5') expect(result[1]).to eq(['b', '2', 'c']) end it 'with match, returns items matching the given pattern' do @client.sadd('set', ['aa', '1', 'b', '2', 'cc', '3', 'd', '4', 'ee', '5', 'f', '6', 'gg', '7']) result = @client.sscan('set', 2, count: 7, match: '??') expect(result[0]).to eq('9') expect(result[1]).to eq(['cc','ee']) end it 'returns an empty result if the key is not found' do result = @client.sscan('set', 0) expect(result[0]).to eq('0') expect(result[1]).to eq([]) end end end end fakeredis-0.8.0/spec/connection_spec.rb0000644000004100000410000000460213627672425020154 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "ConnectionMethods" do before(:each) do @client = Redis.new end if fakeredis? it "should authenticate to the server" do expect(@client.auth("pass")).to eq("OK") end it "should re-use the same instance with the same host & port" do @client1 = Redis.new(:host => "localhost", :port => 1234) @client2 = Redis.new(:host => "localhost", :port => 1234) @client3 = Redis.new(:host => "localhost", :port => 5678) @client1.set("key1", "1") expect(@client2.get("key1")).to eq("1") expect(@client3.get("key1")).to be_nil @client2.set("key2", "2") expect(@client1.get("key2")).to eq("2") expect(@client3.get("key2")).to be_nil @client3.set("key3", "3") expect(@client1.get("key3")).to be_nil expect(@client2.get("key3")).to be_nil expect(@client1.dbsize).to eq(2) expect(@client2.dbsize).to eq(2) expect(@client3.dbsize).to eq(1) end it "should connect to a specific database" do @client1 = Redis.new(:host => "localhost", :port => 1234, :db => 0) @client1.set("key1", "1") @client1.select(0) expect(@client1.get("key1")).to eq("1") @client2 = Redis.new(:host => "localhost", :port => 1234, :db => 1) @client2.set("key1", "1") @client2.select(1) expect(@client2.get("key1")).to eq("1") end it "should not error with shutdown" do expect { @client.shutdown }.not_to raise_error end it "should not error with quit" do expect { @client.quit }.not_to raise_error end end it "should handle multiple clients using the same db instance" do @client1 = Redis.new(:host => "localhost", :port => 6379, :db => 1) @client2 = Redis.new(:host => "localhost", :port => 6379, :db => 2) @client1.set("key1", "one") expect(@client1.get("key1")).to eq("one") @client2.set("key2", "two") expect(@client2.get("key2")).to eq("two") expect(@client1.get("key1")).to eq("one") end it "should not error with a disconnected client" do @client1 = Redis.new @client1.close expect(@client1.get("key1")).to be_nil end it "should echo the given string" do expect(@client.echo("something")).to eq("something") end it "should ping the server" do expect(@client.ping).to eq("PONG") end end end fakeredis-0.8.0/spec/sort_method_spec.rb0000644000004100000410000000321513627672425020343 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "#sort" do before(:each) do @client = Redis.new @client.set('fake-redis-test:values_1', 'a') @client.set('fake-redis-test:values_2', 'b') @client.set('fake-redis-test:weight_1', '2') @client.set('fake-redis-test:weight_2', '1') @client.hset('fake-redis-test:hash_1', 'key', 'x') @client.hset('fake-redis-test:hash_2', 'key', 'y') end context "WRONGTYPE Operation" do it "should not allow #sort on Strings" do @client.set("key1", "Hello") expect { @client.sort("key1") }.to raise_error(Redis::CommandError) end it "should not allow #sort on Hashes" do @client.hset("key1", "k1", "val1") @client.hset("key1", "k2", "val2") expect { @client.sort("key1") }.to raise_error(Redis::CommandError) end end context "none" do it "should return empty array" do expect(@client.sort("key")).to eq [] end end context "list" do before do @key = "fake-redis-test:list_sort" @client.rpush(@key, '1') @client.rpush(@key, '2') end it_should_behave_like "a sortable" end context "set" do before do @key = "fake-redis-test:set_sort" @client.sadd(@key, '1') @client.sadd(@key, '2') end it_should_behave_like "a sortable" end context "zset" do before do @key = "fake-redis-test:zset_sort" @client.zadd(@key, 100, '1') @client.zadd(@key, 99, '2') end it_should_behave_like "a sortable" end end end fakeredis-0.8.0/spec/sorted_sets_spec.rb0000644000004100000410000010044713627672425020357 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis Infinity = 1.0/0.0 describe "SortedSetsMethods" do before(:each) do @client = Redis.new end it "should add a member to a sorted set, or update its score if it already exists" do expect(@client.zadd("key", 1, "val")).to be(true) expect(@client.zscore("key", "val")).to eq(1.0) expect(@client.zadd("key", 2, "val")).to be(false) expect(@client.zscore("key", "val")).to eq(2.0) # These assertions only work in redis-rb v3.0.2 or higher expect(@client.zadd("key2", "inf", "val")).to eq(true) expect(@client.zscore("key2", "val")).to eq(Infinity) expect(@client.zadd("key3", "+inf", "val")).to eq(true) expect(@client.zscore("key3", "val")).to eq(Infinity) expect(@client.zadd("key4", "-inf", "val")).to eq(true) expect(@client.zscore("key4", "val")).to eq(-Infinity) end it "should return a nil score for value not in a sorted set or empty key" do @client.zadd "key", 1, "val" expect(@client.zscore("key", "val")).to eq(1.0) expect(@client.zscore("key", "val2")).to be_nil expect(@client.zscore("key2", "val")).to be_nil end it "should add multiple members to a sorted set, or update its score if it already exists" do expect(@client.zadd("key", [1, "val", 2, "val2"])).to eq(2) expect(@client.zscore("key", "val")).to eq(1) expect(@client.zscore("key", "val2")).to eq(2) expect(@client.zadd("key", [[5, "val"], [3, "val3"], [4, "val4"]])).to eq(2) expect(@client.zscore("key", "val")).to eq(5) expect(@client.zscore("key", "val2")).to eq(2) expect(@client.zscore("key", "val3")).to eq(3) expect(@client.zscore("key", "val4")).to eq(4) expect(@client.zadd("key", [[5, "val5"], [3, "val6"]])).to eq(2) expect(@client.zscore("key", "val5")).to eq(5) expect(@client.zscore("key", "val6")).to eq(3) end it "should error with wrong number of arguments when adding members" do expect { @client.zadd("key") }.to raise_error(ArgumentError, "wrong number of arguments") expect { @client.zadd("key", 1) }.to raise_error(ArgumentError, "wrong number of arguments") expect { @client.zadd("key", [1]) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for 'zadd' command") expect { @client.zadd("key", [1, "val", 2]) }.to raise_error(Redis::CommandError, "ERR syntax error") expect { @client.zadd("key", [[1, "val"], [2]]) }.to raise_error(Redis::CommandError, "ERR syntax error") end it "should allow floats as scores when adding or updating" do expect(@client.zadd("key", 4.321, "val")).to be(true) expect(@client.zscore("key", "val")).to eq(4.321) expect(@client.zadd("key", 54.3210, "val")).to be(false) expect(@client.zscore("key", "val")).to eq(54.321) end it "should allow strings that can be parsed as float when adding or updating" do expect(@client.zadd("key", "4.321", "val")).to be(true) expect(@client.zscore("key", "val")).to eq(4.321) expect(@client.zadd("key", "54.3210", "val")).to be(false) expect(@client.zscore("key", "val")).to eq(54.321) end it "should error when the score is not a valid float" do expect { @client.zadd("key", "score", "val") }.to raise_error(Redis::CommandError, "ERR value is not a valid float") expect { @client.zadd("key", {}, "val") }.to raise_error(Redis::CommandError, "ERR value is not a valid float") expect { @client.zadd("key", Time.now, "val") }.to raise_error(Redis::CommandError, "ERR value is not a valid float") end it "should remove members from sorted sets" do expect(@client.zrem("key", "val")).to be(false) expect(@client.zadd("key", 1, "val")).to be(true) expect(@client.zrem("key", "val")).to be(true) end it "should remove multiple members from sorted sets" do expect(@client.zrem("key2", %w(val))).to eq(0) expect(@client.zrem("key2", %w(val val2 val3))).to eq(0) @client.zadd("key2", 1, "val") @client.zadd("key2", 1, "val2") @client.zadd("key2", 1, "val3") expect(@client.zrem("key2", %w(val val2 val3 val4))).to eq(3) end it "should remove sorted set's key when it is empty" do @client.zadd("key", 1, "val") @client.zrem("key", "val") expect(@client.exists("key")).to eq(false) end it "should pop members with the highest scores from sorted set" do @client.zadd("key", [1, "val1", 2, "val2", 3, "val3"]) expect(@client.zpopmax("key")).to eq(["val3", 3.0]) expect(@client.zpopmax("key", 3)).to eq([["val2", 2.0], ["val1", 1.0]]) expect(@client.zpopmax("nonexistent")).to eq(nil) end it "should pop members with the lowest scores from sorted set" do @client.zadd("key", [1, "val1", 2, "val2", 3, "val3"]) expect(@client.zpopmin("key")).to eq(["val1", 1.0]) expect(@client.zpopmin("key", 3)).to eq([["val2", 2.0], ["val3", 3.0]]) expect(@client.zpopmin("nonexistent")).to eq(nil) end it "should pop members with the highest score from first sorted set that is non-empty" do @client.zadd("key1", [1, "val1", 2, "val2"]) @client.zadd("key2", [3, "val3"]) expect(@client.bzpopmax("nonexistent", "key1", "key2", 0)).to eq(["key1", "val2", 2.0]) expect(@client.bzpopmax("nonexistent")).to eq(nil) end it "should pop members with the lowest score from first sorted set that is non-empty" do @client.zadd("key1", [1, "val1", 2, "val2"]) @client.zadd("key2", [3, "val3"]) expect(@client.bzpopmin("nonexistent", "key1", "key2", 0)).to eq(["key1", "val1", 1.0]) expect(@client.bzpopmin("nonexistent")).to eq(nil) end it "should get the number of members in a sorted set" do @client.zadd("key", 1, "val2") @client.zadd("key", 2, "val1") @client.zadd("key", 5, "val3") expect(@client.zcard("key")).to eq(3) end it "should count the members in a sorted set with scores within the given values" do @client.zadd("key", 1, "val1") @client.zadd("key", 2, "val2") @client.zadd("key", 3, "val3") expect(@client.zcount("key", 2, 3)).to eq(2) end it "should increment the score of a member in a sorted set" do @client.zadd("key", 1, "val1") expect(@client.zincrby("key", 2, "val1")).to eq(3) expect(@client.zscore("key", "val1")).to eq(3) end it "initializes the sorted set if the key wasnt already set" do expect(@client.zincrby("key", 1, "val1")).to eq(1) end it "should convert the key to a string for zscore" do @client.zadd("key", 1, 1) expect(@client.zscore("key", 1)).to eq(1) end # These won't pass until redis-rb releases v3.0.2 it "should handle infinity values when incrementing a sorted set key" do expect(@client.zincrby("bar", "+inf", "s2")).to eq(Infinity) expect(@client.zincrby("bar", "-inf", "s1")).to eq(-Infinity) end it "should return a range of members in a sorted set, by index" do @client.zadd("key", 1, "one") @client.zadd("key", 2, "two") @client.zadd("key", 3, "three") expect(@client.zrange("key", 0, -1)).to eq(["one", "two", "three"]) expect(@client.zrange("key", 1, 2)).to eq(["two", "three"]) expect(@client.zrange("key", -50, -2)).to eq(["one", "two"]) expect(@client.zrange("key", 0, -1, :withscores => true)).to eq([["one", 1], ["two", 2], ["three", 3]]) expect(@client.zrange("key", 1, 2, :with_scores => true)).to eq([["two", 2], ["three", 3]]) end it "should sort zrange results logically" do @client.zadd("key", 5, "val2") @client.zadd("key", 5, "val3") @client.zadd("key", 5, "val1") expect(@client.zrange("key", 0, -1)).to eq(%w(val1 val2 val3)) expect(@client.zrange("key", 0, -1, :with_scores => true)).to eq([["val1", 5], ["val2", 5], ["val3", 5]]) end it "should return a reversed range of members in a sorted set, by index" do @client.zadd("key", 1, "one") @client.zadd("key", 2, "two") @client.zadd("key", 3, "three") expect(@client.zrevrange("key", 0, -1)).to eq(["three", "two", "one"]) expect(@client.zrevrange("key", 1, 2)).to eq(["two", "one"]) expect(@client.zrevrange("key", 0, -1, :withscores => true)).to eq([["three", 3], ["two", 2], ["one", 1]]) expect(@client.zrevrange("key", 0, -1, :with_scores => true)).to eq([["three", 3], ["two", 2], ["one", 1]]) end it "should return a range of members in a sorted set, by score" do @client.zadd("key", 1, "one") @client.zadd("key", 2, "two") @client.zadd("key", 3, "three") expect(@client.zrangebyscore("key", 0, 100)).to eq(["one", "two", "three"]) expect(@client.zrangebyscore("key", 1, 2)).to eq(["one", "two"]) expect(@client.zrangebyscore("key", 1, '(2')).to eq(['one']) expect(@client.zrangebyscore("key", '(1', 2)).to eq(['two']) expect(@client.zrangebyscore("key", '(1', '(2')).to eq([]) expect(@client.zrangebyscore("key", 0, 100, :withscores => true)).to eq([["one", 1], ["two", 2], ["three", 3]]) expect(@client.zrangebyscore("key", 1, 2, :with_scores => true)).to eq([["one", 1], ["two", 2]]) expect(@client.zrangebyscore("key", 0, 100, :limit => [0, 1])).to eq(["one"]) expect(@client.zrangebyscore("key", 0, 100, :limit => [0, -1])).to eq(["one", "two", "three"]) expect(@client.zrangebyscore("key", 0, 100, :limit => [1, -1], :with_scores => true)).to eq([["two", 2], ["three", 3]]) expect(@client.zrangebyscore("key", '-inf', '+inf')).to eq(["one", "two", "three"]) expect(@client.zrangebyscore("key", 2, '+inf')).to eq(["two", "three"]) expect(@client.zrangebyscore("key", '-inf', 2)).to eq(['one', "two"]) expect(@client.zrangebyscore("badkey", 1, 2)).to eq([]) end it "should return a reversed range of members in a sorted set, by score" do @client.zadd("key", 1, "one") @client.zadd("key", 2, "two") @client.zadd("key", 3, "three") expect(@client.zrevrangebyscore("key", 100, 0)).to eq(["three", "two", "one"]) expect(@client.zrevrangebyscore("key", 2, 1)).to eq(["two", "one"]) expect(@client.zrevrangebyscore("key", 1, 2)).to eq([]) expect(@client.zrevrangebyscore("key", 2, 1, :with_scores => true)).to eq([["two", 2.0], ["one", 1.0]]) expect(@client.zrevrangebyscore("key", 100, 0, :limit => [0, 1])).to eq(["three"]) expect(@client.zrevrangebyscore("key", 100, 0, :limit => [0, -1])).to eq(["three", "two", "one"]) expect(@client.zrevrangebyscore("key", 100, 0, :limit => [1, -1], :with_scores => true)).to eq([["two", 2.0], ["one", 1.0]]) end it "should determine the index of a member in a sorted set" do @client.zadd("key", 1, "one") @client.zadd("key", 2, "two") @client.zadd("key", 3, "three") expect(@client.zrank("key", "three")).to eq(2) expect(@client.zrank("key", "four")).to be_nil end it "should determine the reversed index of a member in a sorted set" do @client.zadd("key", 1, "one") @client.zadd("key", 2, "two") @client.zadd("key", 3, "three") expect(@client.zrevrank("key", "three")).to eq(0) expect(@client.zrevrank("key", "four")).to be_nil end it "should not raise errors for zrank() on accessing a non-existing key in a sorted set" do expect(@client.zrank("no_such_key", "no_suck_id")).to be_nil end it "should not raise errors for zrevrank() on accessing a non-existing key in a sorted set" do expect(@client.zrevrank("no_such_key", "no_suck_id")).to be_nil end describe "#zinterstore" do before do @client.zadd("key1", 1, "one") @client.zadd("key1", 2, "two") @client.zadd("key1", 3, "three") @client.zadd("key2", 5, "two") @client.zadd("key2", 7, "three") @client.sadd("key3", 'one') @client.sadd("key3", 'two') end it "should intersect two keys with custom scores" do expect(@client.zinterstore("out", ["key1", "key2"])).to eq(2) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([['two', (2 + 5)], ['three', (3 + 7)]]) end it "should intersect two keys with a default score" do expect(@client.zinterstore("out", ["key1", "key3"])).to eq(2) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([['one', (1 + 1)], ['two', (2 + 1)]]) end it "should intersect more than two keys" do expect(@client.zinterstore("out", ["key1", "key2", "key3"])).to eq(1) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([['two', (2 + 5 + 1)]]) end it "should not intersect an unknown key" do expect(@client.zinterstore("out", ["key1", "no_key"])).to eq(0) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([]) end it "should intersect two keys by minimum values" do expect(@client.zinterstore("out", ["key1", "key2"], :aggregate => :min)).to eq(2) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["two", 2], ["three", 3]]) end it "should intersect two keys by maximum values" do expect(@client.zinterstore("out", ["key1", "key2"], :aggregate => :max)).to eq(2) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["two", 5], ["three", 7]]) end it "should intersect two keys by explicitly summing values" do expect(@client.zinterstore("out", %w(key1 key2), :aggregate => :sum)).to eq(2) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["two", (2 + 5)], ["three", (3 + 7)]]) end it "should intersect two keys with weighted values" do expect(@client.zinterstore("out", %w(key1 key2), :weights => [10, 1])).to eq(2) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["two", (2 * 10 + 5)], ["three", (3 * 10 + 7)]]) end it "should intersect two keys with weighted minimum values" do expect(@client.zinterstore("out", %w(key1 key2), :weights => [10, 1], :aggregate => :min)).to eq(2) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["two", 5], ["three", 7]]) end it "should intersect two keys with weighted maximum values" do expect(@client.zinterstore("out", %w(key1 key2), :weights => [10, 1], :aggregate => :max)).to eq(2) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["two", (2 * 10)], ["three", (3 * 10)]]) end it "should error without enough weights given" do expect { @client.zinterstore("out", %w(key1 key2), :weights => []) }.to raise_error(Redis::CommandError, "ERR syntax error") expect { @client.zinterstore("out", %w(key1 key2), :weights => [10]) }.to raise_error(Redis::CommandError, "ERR syntax error") end it "should error with too many weights given" do expect { @client.zinterstore("out", %w(key1 key2), :weights => [10, 1, 1]) }.to raise_error(Redis::CommandError, "ERR syntax error") end it "should error with an invalid aggregate" do expect { @client.zinterstore("out", %w(key1 key2), :aggregate => :invalid) }.to raise_error(Redis::CommandError, "ERR syntax error") end it 'stores nothing when there are no members in common' do @client.zadd("k1", 1, "1") @client.zadd("k1", 1, "2") @client.sadd("k2", "a") @client.sadd("k3", "b") expect(@client.zinterstore("out", %w(k1 k2 k3))).to eq(0) expect(@client.zrange("out", 0, -1)).to eq([]) end it 'handles range start being higher than number of members' do @client.zadd("key", 1, "1") expect(@client.zrange("key", 10, 10)).to eq([]) end end context "zremrangebyscore" do it "should remove items by score" do @client.zadd("key", 1, "one") @client.zadd("key", 2, "two") @client.zadd("key", 3, "three") expect(@client.zremrangebyscore("key", 0, 2)).to eq(2) expect(@client.zcard("key")).to eq(1) end it "should remove items by score with infinity" do # Issue #50 @client.zadd("key", 10.0, "one") @client.zadd("key", 20.0, "two") @client.zadd("key", 30.0, "three") expect(@client.zremrangebyscore("key", "-inf", "+inf")).to eq(3) expect(@client.zcard("key")).to eq(0) expect(@client.zscore("key", "one")).to be_nil expect(@client.zscore("key", "two")).to be_nil expect(@client.zscore("key", "three")).to be_nil end it "should return 0 if the key didn't exist" do expect(@client.zremrangebyscore("key", 0, 2)).to eq(0) end end context '#zremrangebyrank' do it 'removes all elements with in the given range' do @client.zadd("key", 1, "one") @client.zadd("key", 2, "two") @client.zadd("key", 3, "three") expect(@client.zremrangebyrank("key", 0, 1)).to eq(2) expect(@client.zcard('key')).to eq(1) end it 'handles out of range requests' do @client.zadd("key", 1, "one") @client.zadd("key", 2, "two") @client.zadd("key", 3, "three") expect(@client.zremrangebyrank("key", 25, -1)).to eq(0) expect(@client.zcard('key')).to eq(3) end it "should return 0 if the key didn't exist" do expect(@client.zremrangebyrank("key", 0, 1)).to eq(0) end end describe "#zunionstore" do before do @client.zadd("key1", 1, "val1") @client.zadd("key1", 2, "val2") @client.zadd("key1", 3, "val3") @client.zadd("key2", 5, "val2") @client.zadd("key2", 7, "val3") @client.sadd("key3", "val1") @client.sadd("key3", "val2") end it "should union two keys with custom scores" do expect(@client.zunionstore("out", %w(key1 key2))).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val1", 1], ["val2", (2 + 5)], ["val3", (3 + 7)]]) end it "should union two keys with a default score" do expect(@client.zunionstore("out", %w(key1 key3))).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val1", (1 + 1)], ["val2", (2 + 1)], ["val3", 3]]) end it "should union more than two keys" do expect(@client.zunionstore("out", %w(key1 key2 key3))).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val1", (1 + 1)], ["val2", (2 + 5 + 1)], ["val3", (3 + 7)]]) end it "should union with an unknown key" do expect(@client.zunionstore("out", %w(key1 no_key))).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val1", 1], ["val2", 2], ["val3", 3]]) end it "should union two keys by minimum values" do expect(@client.zunionstore("out", %w(key1 key2), :aggregate => :min)).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val1", 1], ["val2", 2], ["val3", 3]]) end it "should union two keys by maximum values" do expect(@client.zunionstore("out", %w(key1 key2), :aggregate => :max)).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val1", 1], ["val2", 5], ["val3", 7]]) end it "should union two keys by explicitly summing values" do expect(@client.zunionstore("out", %w(key1 key2), :aggregate => :sum)).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val1", 1], ["val2", (2 + 5)], ["val3", (3 + 7)]]) end it "should union two keys with weighted values" do expect(@client.zunionstore("out", %w(key1 key2), :weights => [10, 1])).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val1", (1 * 10)], ["val2", (2 * 10 + 5)], ["val3", (3 * 10 + 7)]]) end it "should union two keys with weighted minimum values" do expect(@client.zunionstore("out", %w(key1 key2), :weights => [10, 1], :aggregate => :min)).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val2", 5], ["val3", 7], ["val1", (1 * 10)]]) end it "should union two keys with weighted maximum values" do expect(@client.zunionstore("out", %w(key1 key2), :weights => [10, 1], :aggregate => :max)).to eq(3) expect(@client.zrange("out", 0, -1, :with_scores => true)).to eq([["val1", (1 * 10)], ["val2", (2 * 10)], ["val3", (3 * 10)]]) end it "should error without enough weights given" do expect { @client.zunionstore("out", %w(key1 key2), :weights => []) }.to raise_error(Redis::CommandError, "ERR syntax error") expect { @client.zunionstore("out", %w(key1 key2), :weights => [10]) }.to raise_error(Redis::CommandError, "ERR syntax error") end it "should error with too many weights given" do expect { @client.zunionstore("out", %w(key1 key2), :weights => [10, 1, 1]) }.to raise_error(Redis::CommandError, "ERR syntax error") end it "should error with an invalid aggregate" do expect { @client.zunionstore("out", %w(key1 key2), :aggregate => :invalid) }.to raise_error(Redis::CommandError, "ERR syntax error") end end #it "should remove all members in a sorted set within the given indexes" #it "should return a range of members in a sorted set, by index, with scores ordered from high to low" #it "should return a range of members in a sorted set, by score, with scores ordered from high to low" #it "should determine the index of a member in a sorted set, with scores ordered from high to low" #it "should get the score associated with the given member in a sorted set" #it "should add multiple sorted sets and store the resulting sorted set in a new key" it "zrem should remove members add by zadd" do @client.zadd("key1", 1, 3) @client.zrem("key1", 3) expect(@client.zscore("key1", 3)).to be_nil end describe "#zscan" do before { 50.times { |x| @client.zadd("key", x, "key #{x}") } } it 'with no arguments should return 10 numbers in ascending order' do result = @client.zscan("key", 0)[1] expect(result).to eq(result.sort { |x, y| x[1] <=> y[1] }) expect(result.count).to eq(10) end it 'with a count should return that number of members' do expect(@client.zscan("key", 0, count: 2)).to eq(["2", [["key 0", 0.0], ["key 1", 1.0]]]) end it 'with a count greater than the number of members, returns all the members in asc order' do result = @client.zscan("key", 0, count: 1000)[1] expect(result).to eq(result.sort { |x, y| x[1] <=> y[1] }) expect(result.size).to eq(50) end it 'with match, should return key-values where the key matches' do @client.zadd("key", 1.0, "blah") @client.zadd("key", 2.0, "bluh") result = @client.zscan("key", 0, count: 100, match: "key*")[1] expect(result).to_not include(["blah", 1.0]) expect(result).to_not include(["bluh", 2.0]) end end describe "#zscan_each" do before { 50.times { |x| @client.zadd("key", x, "key #{x}") } } it 'enumerates over the items in the sorted set' do expect(@client.zscan_each("key").to_a).to eq(@client.zscan("key", 0, count: 50)[1]) end end describe '#zrangebylex' do before { @client.zadd "myzset", [0, 'a', 0, 'b', 0, 'd', 0, 'c'] } it "should return empty list for '+'..'-' range" do ranged = @client.zrangebylex "myzset", "+", "-" expect(ranged).to be_empty end it "should return all elements for '-'..'+' range" do ranged = @client.zrangebylex "myzset", "-", "+" expect(ranged).to eq %w(a b c d) end it "should include values starting with [ symbol" do ranged = @client.zrangebylex "myzset", "-", "[c" expect(ranged).to eq %w(a b c) end it "should exclude values with ( symbol" do ranged = @client.zrangebylex "myzset", "-", "(c" expect(ranged).to eq %w(a b) end it "should work with both [ and ( properly" do ranged = @client.zrangebylex "myzset", "[aaa", "(d" expect(ranged).to eq %w(b c) end it "should return empty array if key is not exist" do ranged = @client.zrangebylex "puppies", "-", "+" expect(ranged).to be_empty end it 'should raise error for invalid range when range is invalid' do expect{ @client.zrangebylex "myzset", "-", "d" }.to raise_error(Redis::CommandError, "ERR min or max not valid string range item") end it "should limit and offset values as 4th argument" do ranged = @client.zrangebylex "myzset", "-", "+", limit: [1, 3] expect(ranged).to eq %w(b c d) end end describe "#zrevrangebylex" do before { @client.zadd "myzset", [0, 'a', 0, 'b', 0, 'd', 0, 'c'] } it "should return empty list for '-'..'+' range" do ranged = @client.zrevrangebylex "myzset", "-", "+" expect(ranged).to be_empty end it "should return all elements for '+'..'-' range in descending order" do ranged = @client.zrevrangebylex "myzset", "+", "-" expect(ranged).to eq %w(d c b a) end it "should include values starting with [ symbol" do ranged = @client.zrevrangebylex "myzset", "[c", "-" expect(ranged).to eq %w(c b a) end it "should exclude values with ( symbol" do ranged = @client.zrevrangebylex "myzset", "+", "(c" expect(ranged).to eq %w(d) end it "should work with both [ and ( properly" do ranged = @client.zrevrangebylex "myzset", "(d", "[aaa" expect(ranged).to eq %w(c b) end it "should return empty array if key does not exist" do ranged = @client.zrevrangebylex "puppies", "+", "-" expect(ranged).to be_empty end it 'should raise error for invalid range when range is invalid' do expect { @client.zrevrangebylex "myzset", "-", "d" }.to raise_error(Redis::CommandError, "ERR min or max not valid string range item") end it "should limit and offset values as 4th argument" do pending "current stable (3.2.0) redis-rb doesn't support limit option" ranged = @client.zrevrangebylex "myzset", "+", "-", limit: [0, 3] expect(ranged).to eq %w(d c b) end end describe "#zadd" do context "with {incr: true}" do before { @client.zadd("key", 1, "existing") } it "should increment the element's score with the provided value" do @client.zadd("key", 99, "existing", incr: true) expect(@client.zscore("key", "existing")).to eq(100.0) @client.zadd("key", 2, "new", incr: true) expect(@client.zscore("key", "new")).to eq(2.0) end it "should error when trying to add multiple increment-element pairs" do expect { @client.zadd("key", [1, "member1", 2, "member2"], incr: true) }.to raise_error(Redis::CommandError, "ERR INCR option supports a single increment-element pair") end end context "with {nx: true}" do before { @client.zadd("key", [1, "existing1", 2, "existing2"]) } it "should add new elements but not update the scores of existing elements" do @client.zadd("key", [101, "existing1", 3, "new"], nx: true) expect(@client.zscore("key", "existing1")).to eq(1.0) expect(@client.zscore("key", "new")).to eq(3.0) @client.zadd("key", 102, "existing2", nx: true) expect(@client.zscore("key", "existing2")).to eq(2.0) end end context "with {xx: true}" do before { @client.zadd("key", 1, "existing") } it "should not add new elements" do expect(@client.zadd("key", 1, "new1", xx: true)).to eq(false) expect(@client.zscore("key", "new1")).to be_nil expect(@client.zadd("key", [11, "existing", 2, "new2"], xx: true)).to eq(0) expect(@client.zscore("key", "existing")).to eq(11.0) expect(@client.zscore("key", "new2")).to be_nil end end context "with {ch: true}" do it "should return the number of new elements added plus the number of existing elements for which the score was updated" do expect(@client.zadd("key", 1, "first", ch: true)).to eq(true) expect(@client.zadd("key", [1, "first", 2, "second"], ch: true)).to eq(1.0) expect(@client.zadd("key", [11, "first", 2, "second"], ch: true)).to eq(1.0) expect(@client.zadd("key", [99, "first", 99, "second"], ch: true)).to eq(2.0) expect(@client.zadd("key", [111, "first", 22, "second", 3, "third"], ch: true)).to eq(3.0) end end context "with {nx: true, xx: true}" do it "should error" do expect{ @client.zadd("key", 1, "value", nx: true, xx: true) }.to raise_error(Redis::CommandError, "ERR XX and NX options at the same time are not compatible") end end context "with {nx: true, incr: true}" do let(:options) { {nx: true, incr: true} } it "should increment to the provided score only if the element is new and return the element's score" do expect(@client.zadd("key", 1, "first", options)).to eq(1.0) expect(@client.zscore("key", "first")).to eq(1.0) expect(@client.zadd("key", 2, "second", options)).to eq(2.0) expect(@client.zscore("key", "second")).to eq(2.0) expect(@client.zadd("key", 99, "first", options)).to be_nil expect(@client.zscore("key", "first")).to eq(1.0) end end context "with {nx: true, ch: true}" do let(:options) { {nx: true, ch: true} } it "should add only new elements, not update existing elements, and return the number of added elements" do expect(@client.zadd("key", 1, "first", options)).to eq(true) expect(@client.zadd("key", 1, "first", options)).to eq(false) # add two new elements expect(@client.zadd("key", [99, "first", 2, "second", 3, "third"], options)).to eq(2) expect(@client.zscore("key", "first")).to eq(1.0) end end context "with {nx: true, incr: true, ch: true}" do let(:options) { {nx: true, incr: true, ch: true} } it "should add only new elements" do expect(@client.zadd("key", 1, "first", options)).to eq(1.0) expect(@client.zadd("key", 99, "first", options)).to be_nil expect(@client.zscore("key", "first")).to eq(1.0) end # when INCR is present, return value is always the new score of member it "should return the score of the new member" do expect(@client.zadd("key", 2, "second", options)).to eq(2.0) end it "should return nil when the member already exists" do @client.zadd("key", 1, "first") expect(@client.zadd("key", 99, "first", options)).to be_nil end end context "with {xx: true, incr: true}" do let(:options) { {xx: true, incr: true} } before { @client.zadd("key", 1, "existing") } it "should return nil if the member does not already exist" do expect(@client.zadd("key", 1, "new1", options)).to be_nil expect(@client.zscore("key", "new1")).to be_nil end it "should increment only existing elements" do expect(@client.zadd("key", [11, "existing"], options)).to eq(12.0) expect(@client.zscore("key", "existing")).to eq(12.0) end end context "with {xx: true, ch: true}" do let(:options) { {xx: true, ch: true} } it "should return the number of updated elements and not add new members" do @client.zadd("key", 1, "first") expect(@client.zadd("key", 99, "first", options)).to eq(true) expect(@client.zadd("key", [100, "first", 2, "second"], options)).to eq(1.0) expect(@client.zscore("key", "second")).to be_nil end end context "with {xx: true, incr: true, ch: true}" do let(:options) { {xx: true, incr: true, ch: true} } before { @client.zadd("key", 1, "existing") } # when INCR is present, return value is always the new score of member it "should return the new score of the inserted member" do expect(@client.zadd("key", 2, "existing", options)).to eq(3.0) end it "should increment only existing elements" do expect(@client.zadd("key", 1, "new", options)).to be_nil end end end end end fakeredis-0.8.0/spec/compatibility_spec.rb0000644000004100000410000000031313627672425020661 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "Compatibility" do it "should be accessible through FakeRedis::Redis" do expect { FakeRedis::Redis.new }.not_to raise_error end end end fakeredis-0.8.0/spec/command_executor_spec.rb0000644000004100000410000000043213627672425021346 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe FakeRedis::CommandExecutor do let(:redis) { Redis.new } context '#write' do it 'does not modify its argument' do command = [:get, 'key'] redis.write(command) expect(command).to eql([:get, 'key']) end end endfakeredis-0.8.0/spec/bitop_command_spec.rb0000644000004100000410000001653313627672425020636 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "#bitop" do before(:all) do @client = Redis.new end before(:each) do @client.discard rescue nil end it 'raises an argument error when passed unsupported operation' do expect { @client.bitop("meh", "dest1", "key1") }.to raise_error(Redis::CommandError) end describe "or" do it_should_behave_like "a bitwise operation", "or" it "should apply bitwise or operation" do @client.setbit("key1", 0, 0) @client.setbit("key1", 1, 1) @client.setbit("key1", 2, 1) @client.setbit("key1", 3, 0) @client.setbit("key2", 0, 1) @client.setbit("key2", 1, 1) @client.setbit("key2", 2, 0) @client.setbit("key2", 3, 0) expect(@client.bitop("or", "dest1", "key1", "key2")).to eq(1) expect(@client.bitcount("dest1")).to eq(3) expect(@client.getbit("dest1", 0)).to eq(1) expect(@client.getbit("dest1", 1)).to eq(1) expect(@client.getbit("dest1", 2)).to eq(1) expect(@client.getbit("dest1", 3)).to eq(0) end it "should apply bitwise or operation with empty values" do @client.setbit("key1", 1, 1) expect(@client.bitop("or", "dest1", "key1", "nothing_here1", "nothing_here2")).to eq(1) expect(@client.bitcount("dest1")).to eq(1) expect(@client.getbit("dest1", 0)).to eq(0) expect(@client.getbit("dest1", 1)).to eq(1) expect(@client.getbit("dest1", 2)).to eq(0) end it "should apply bitwise or operation with multiple keys" do @client.setbit("key1", 1, 1) @client.setbit("key1", 3, 1) @client.setbit("key2", 5, 1) @client.setbit("key2", 10, 1) @client.setbit("key3", 13, 1) @client.setbit("key3", 15, 1) expect(@client.bitop("or", "dest1", "key1", "key2", "key3")).to eq(2) expect(@client.bitcount("dest1")).to eq(6) expect(@client.getbit("dest1", 1)).to eq(1) expect(@client.getbit("dest1", 3)).to eq(1) expect(@client.getbit("dest1", 5)).to eq(1) expect(@client.getbit("dest1", 10)).to eq(1) expect(@client.getbit("dest1", 13)).to eq(1) expect(@client.getbit("dest1", 15)).to eq(1) expect(@client.getbit("dest1", 2)).to eq(0) expect(@client.getbit("dest1", 12)).to eq(0) end end describe "and" do it_should_behave_like "a bitwise operation", "and" it "should apply bitwise and operation" do @client.setbit("key1", 0, 1) @client.setbit("key1", 1, 1) @client.setbit("key1", 2, 0) @client.setbit("key2", 0, 0) @client.setbit("key2", 1, 1) @client.setbit("key2", 2, 1) expect(@client.bitop("and", "dest1", "key1", "key2")).to eq(1) expect(@client.bitcount("dest1")).to eq(1) expect(@client.getbit("dest1", 0)).to eq(0) expect(@client.getbit("dest1", 1)).to eq(1) expect(@client.getbit("dest1", 2)).to eq(0) end it "should apply bitwise and operation with empty values" do @client.setbit("key1", 1, 1) expect(@client.bitop("and", "dest1", "key1", "nothing_here")).to eq(1) expect(@client.bitcount("dest1")).to eq(1) expect(@client.getbit("dest1", 0)).to eq(0) expect(@client.getbit("dest1", 1)).to eq(1) expect(@client.getbit("dest1", 2)).to eq(0) end it "should apply bitwise and operation with multiple keys" do @client.setbit("key1", 1, 1) @client.setbit("key1", 2, 1) @client.setbit("key1", 3, 1) @client.setbit("key1", 4, 1) @client.setbit("key2", 2, 1) @client.setbit("key2", 3, 1) @client.setbit("key2", 4, 1) @client.setbit("key2", 5, 1) @client.setbit("key3", 2, 1) @client.setbit("key3", 4, 1) @client.setbit("key3", 5, 1) @client.setbit("key3", 6, 1) expect(@client.bitop("and", "dest1", "key1", "key2", "key3")).to eq(1) expect(@client.bitcount("dest1")).to eq(2) expect(@client.getbit("dest1", 1)).to eq(0) expect(@client.getbit("dest1", 2)).to eq(1) expect(@client.getbit("dest1", 3)).to eq(0) expect(@client.getbit("dest1", 4)).to eq(1) expect(@client.getbit("dest1", 5)).to eq(0) expect(@client.getbit("dest1", 6)).to eq(0) end end describe "xor" do it_should_behave_like "a bitwise operation", "xor" it "should apply bitwise xor operation" do @client.setbit("key1", 0, 0) @client.setbit("key1", 1, 1) @client.setbit("key1", 2, 0) @client.setbit("key1", 3, 0) @client.setbit("key2", 0, 1) @client.setbit("key2", 1, 1) @client.setbit("key2", 2, 1) @client.setbit("key2", 3, 0) expect(@client.bitop("xor", "dest1", "key1", "key2")).to eq(1) expect(@client.bitcount("dest1")).to eq(2) expect(@client.getbit("dest1", 0)).to eq(1) expect(@client.getbit("dest1", 1)).to eq(0) expect(@client.getbit("dest1", 2)).to eq(1) expect(@client.getbit("dest1", 3)).to eq(0) end it "should apply bitwise xor operation with empty values" do @client.setbit("key1", 1, 1) expect(@client.bitop("xor", "dest1", "key1", "nothing_here1", "nothing_here2")).to eq(1) expect(@client.bitcount("dest1")).to eq(1) expect(@client.getbit("dest1", 0)).to eq(0) expect(@client.getbit("dest1", 1)).to eq(1) expect(@client.getbit("dest1", 2)).to eq(0) end it "should apply bitwise xor operation with multiple keys" do @client.setbit("key1", 1, 1) @client.setbit("key1", 3, 1) @client.setbit("key1", 5, 1) @client.setbit("key1", 6, 1) @client.setbit("key2", 2, 1) @client.setbit("key2", 3, 1) @client.setbit("key2", 4, 1) @client.setbit("key2", 6, 1) expect(@client.bitop("xor", "dest1", "key1", "key2")).to eq(1) expect(@client.bitcount("dest1")).to eq(4) expect(@client.getbit("dest1", 1)).to eq(1) expect(@client.getbit("dest1", 2)).to eq(1) expect(@client.getbit("dest1", 3)).to eq(0) expect(@client.getbit("dest1", 4)).to eq(1) expect(@client.getbit("dest1", 5)).to eq(1) expect(@client.getbit("dest1", 6)).to eq(0) end end describe "not" do it 'raises an argument error when not passed any keys' do expect { @client.bitop("not", "destkey") }.to raise_error(Redis::CommandError) end it 'raises an argument error when not passed too many keys' do expect { @client.bitop("not", "destkey", "key1", "key2") }.to raise_error(Redis::CommandError) end it "should apply bitwise negation operation" do @client.setbit("key1", 1, 1) @client.setbit("key1", 3, 1) @client.setbit("key1", 5, 1) expect(@client.bitop("not", "dest1", "key1")).to eq(1) expect(@client.bitcount("dest1")).to eq(5) expect(@client.getbit("dest1", 0)).to eq(1) expect(@client.getbit("dest1", 1)).to eq(0) expect(@client.getbit("dest1", 2)).to eq(1) expect(@client.getbit("dest1", 3)).to eq(0) expect(@client.getbit("dest1", 4)).to eq(1) expect(@client.getbit("dest1", 5)).to eq(0) expect(@client.getbit("dest1", 6)).to eq(1) expect(@client.getbit("dest1", 7)).to eq(1) end end end end fakeredis-0.8.0/spec/server_spec.rb0000644000004100000410000000600113627672425017316 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "ServerMethods" do before(:each) do @client = Redis.new end it "should return the number of keys in the selected database" do @client.set("key1", "1") @client.set("key2", "2") @client.set("key2", "two") expect(@client.dbsize).to eq(2) end it "should get information and statistics about the server" do expect(@client.info.key?("redis_version")).to eq(true) end it "should handle non-existent methods" do expect { @client.idontexist }.to raise_error(Redis::CommandError, "ERR unknown command 'idontexist'") end describe "multiple databases" do it "should default to database 0" do expect(@client.inspect).to match(%r#/0>$#) end it "should select another database" do @client.select(1) expect(@client.inspect).to match(%r#/1>$#) end it "should store keys separately in each database" do expect(@client.select(0)).to eq("OK") @client.set("key1", "1") @client.set("key2", "2") @client.select(1) @client.set("key3", "3") @client.set("key4", "4") @client.set("key5", "5") @client.select(0) expect(@client.dbsize).to eq(2) expect(@client.exists("key1")).to be true expect(@client.exists("key3")).to be false @client.select(1) expect(@client.dbsize).to eq(3) expect(@client.exists("key4")).to be true expect(@client.exists("key2")).to be false @client.flushall expect(@client.dbsize).to eq(0) @client.select(0) expect(@client.dbsize).to eq(0) end it "should flush a database" do @client.select(0) @client.set("key1", "1") @client.set("key2", "2") expect(@client.dbsize).to eq(2) @client.select(1) @client.set("key3", "3") @client.set("key4", "4") expect(@client.dbsize).to eq(2) expect(@client.flushdb).to eq("OK") expect(@client.dbsize).to eq(0) @client.select(0) expect(@client.dbsize).to eq(2) end it "should flush all databases" do @client.select(0) @client.set("key3", "3") @client.set("key4", "4") expect(@client.dbsize).to eq(2) @client.select(1) @client.set("key3", "3") @client.set("key4", "4") expect(@client.dbsize).to eq(2) expect(@client.flushall).to eq("OK") expect(@client.dbsize).to eq(0) @client.select(0) expect(@client.dbsize).to eq(0) end end end describe 'custom options' do describe 'version' do it 'reports default Redis version when not provided' do client = Redis.new expect(client.info['redis_version']).to eq Redis::Connection::DEFAULT_REDIS_VERSION end it 'creates with and reports properly' do client = Redis.new(version: '3.3.0') expect(client.info['redis_version']).to eq '3.3.0' end end end end fakeredis-0.8.0/spec/keys_spec.rb0000644000004100000410000003622513627672425016776 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "KeysMethods" do before(:each) do @client = Redis.new end [:del, :unlink].each do |command| it "should #{command} a key" do @client.set("key1", "1") @client.set("key2", "2") @client.public_send(command, "key1", "key2") expect(@client.get("key1")).to eq(nil) end it "should #{command} multiple keys" do @client.set("key1", "1") @client.set("key2", "2") @client.public_send(command, ["key1", "key2"]) expect(@client.get("key1")).to eq(nil) expect(@client.get("key2")).to eq(nil) end it "should return the number of '#{command}'ed keys" do @client.set("key1", "1") expect(@client.public_send(command, ["key1", "key2"])).to eq(1) end it "should error '#{command}'ing no keys" do expect { @client.public_send(command) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for '#{command}' command") expect { @client.public_send(command, []) }.to raise_error(Redis::CommandError, "ERR wrong number of arguments for '#{command}' command") end end it "should return true when setnx keys that don't exist" do expect(@client.setnx("key1", "1")).to eq(true) end it "should return false when setnx keys exist" do @client.set("key1", "1") expect(@client.setnx("key1", "1")).to eq(false) end it "should return true when setting expires on keys that exist" do @client.set("key1", "1") expect(@client.expire("key1", 1)).to eq(true) end it "should return true when setting pexpires on keys that exist" do @client.set("key1", "1") expect(@client.pexpire("key1", 1)).to eq(true) end it "should return false when attempting to set expires on a key that does not exist" do expect(@client.expire("key1", 1)).to eq(false) end it "should return false when attempting to set pexpires on a key that does not exist" do expect(@client.pexpire("key1", 1)).to eq(false) end it "should determine if a key exists" do @client.set("key1", "1") expect(@client.exists("key1")).to eq(true) expect(@client.exists("key2")).to eq(false) end it "should set a key's time to live in seconds" do @client.set("key1", "1") @client.expire("key1", 1) expect(@client.ttl("key1")).to eq(1) end it "should set a key's time to live in miliseconds" do allow(Time).to receive(:now).and_return(1000) @client.set("key1", "1") @client.pexpire("key1", 2200) expect(@client.pttl("key1")).to be_within(0.1).of(2200) allow(Time).to receive(:now).and_call_original end it "should set the expiration for a key as a UNIX timestamp" do @client.set("key1", "1") @client.expireat("key1", Time.now.to_i + 2) expect(@client.ttl("key1")).to eq(2) end it "should not have an expiration after re-set" do @client.set("key1", "1") @client.expireat("key1", Time.now.to_i + 2) @client.set("key1", "1") expect(@client.ttl("key1")).to eq(-1) end it "should not have a ttl if expired (and thus key does not exist)" do @client.set("key1", "1") @client.expireat("key1", Time.now.to_i) expect(@client.ttl("key1")).to eq(-2) end it "should not find a key if expired" do @client.set("key1", "1") @client.expireat("key1", Time.now.to_i) expect(@client.get("key1")).to be_nil end it "should not find multiple keys if expired" do @client.set("key1", "1") @client.set("key2", "2") @client.expireat("key1", Time.now.to_i) expect(@client.mget("key1", "key2")).to eq([nil, "2"]) end it "should only find keys that aren't expired" do @client.set("key1", "1") @client.set("key2", "2") @client.expireat("key1", Time.now.to_i) expect(@client.keys).to eq(["key2"]) end it "should not exist if expired" do @client.set("key1", "1") @client.expireat("key1", Time.now.to_i) expect(@client.exists("key1")).to be false end it "should get integer and string keys" do @client.set("key1", "1") @client.set(2, "2") expect(@client.mget("key1", 2)).to eq(["1", "2"]) end it "should find all keys matching the given pattern" do @client.set("key:a", "1") @client.set("key:b", "2") @client.set("key:c", "3") @client.set("akeyd", "4") @client.set("key1", "5") @client.mset("database", 1, "above", 2, "suitability", 3, "able", 4) expect(@client.keys("key:*")).to match_array(["key:a", "key:b", "key:c"]) expect(@client.keys("ab*")).to match_array(["above", "able"]) end it "should remove the expiration from a key" do @client.set("key1", "1") @client.expireat("key1", Time.now.to_i + 1) expect(@client.persist("key1")).to eq(true) expect(@client.persist("key1")).to eq(false) expect(@client.ttl("key1")).to eq(-1) end it "should return a random key from the keyspace" do @client.set("key1", "1") @client.set("key2", "2") expect(["key1", "key2"].include?(@client.randomkey)).to eq(true) end it "should rename a key" do @client.set("key1", "2") @client.rename("key1", "key2") expect(@client.get("key1")).to eq(nil) expect(@client.get("key2")).to eq("2") end it "should rename a key, only if new key does not exist" do @client.set("key1", "1") @client.set("key2", "2") @client.set("key3", "3") @client.renamenx("key1", "key2") @client.renamenx("key3", "key4") expect(@client.get("key1")).to eq("1") expect(@client.get("key2")).to eq("2") expect(@client.get("key3")).to eq(nil) expect(@client.get("key4")).to eq("3") end it "should determine the type stored at key" do # Non-existing key expect(@client.type("key0")).to eq("none") # String @client.set("key1", "1") expect(@client.type("key1")).to eq("string") # List @client.lpush("key2", "1") expect(@client.type("key2")).to eq("list") # Set @client.sadd("key3", "1") expect(@client.type("key3")).to eq("set") # Sorted Set @client.zadd("key4", 1.0, "1") expect(@client.type("key4")).to eq("zset") # Hash @client.hset("key5", "a", "1") expect(@client.type("key5")).to eq("hash") end it "should convert the value into a string before storing" do @client.set("key1", 1) expect(@client.get("key1")).to eq("1") @client.setex("key2", 30, 1) expect(@client.get("key2")).to eq("1") @client.getset("key3", 1) expect(@client.get("key3")).to eq("1") end it "should return 'OK' for the setex command" do expect(@client.setex("key4", 30, 1)).to eq("OK") end it "should convert the key into a string before storing" do @client.set(123, "foo") expect(@client.keys).to include("123") expect(@client.get("123")).to eq("foo") @client.setex(456, 30, "foo") expect(@client.keys).to include("456") expect(@client.get("456")).to eq("foo") @client.getset(789, "foo") expect(@client.keys).to include("789") expect(@client.get("789")).to eq("foo") end it "should only operate against keys containing string values" do @client.sadd("key1", "one") expect { @client.get("key1") }.to raise_error(Redis::CommandError, "WRONGTYPE Operation against a key holding the wrong kind of value") expect { @client.getset("key1", 1) }.to raise_error(Redis::CommandError, "WRONGTYPE Operation against a key holding the wrong kind of value") @client.hset("key2", "one", "two") expect { @client.get("key2") }.to raise_error(Redis::CommandError, "WRONGTYPE Operation against a key holding the wrong kind of value") expect { @client.getset("key2", 1) }.to raise_error(Redis::CommandError, "WRONGTYPE Operation against a key holding the wrong kind of value") end it "should move a key from one database to another successfully" do @client.select(0) @client.set("key1", "1") expect(@client.move("key1", 1)).to eq(true) @client.select(0) expect(@client.get("key1")).to be_nil @client.select(1) expect(@client.get("key1")).to eq("1") end it "should fail to move a key that does not exist in the source database" do @client.select(0) expect(@client.get("key1")).to be_nil expect(@client.move("key1", 1)).to eq(false) @client.select(0) expect(@client.get("key1")).to be_nil @client.select(1) expect(@client.get("key1")).to be_nil end it "should fail to move a key that exists in the destination database" do @client.select(0) @client.set("key1", "1") @client.select(1) @client.set("key1", "2") @client.select(0) expect(@client.move("key1", 1)).to eq(false) @client.select(0) expect(@client.get("key1")).to eq("1") @client.select(1) expect(@client.get("key1")).to eq("2") end it "should fail to move a key to the same database" do @client.select(0) @client.set("key1", "1") expect { @client.move("key1", 0) }.to raise_error(Redis::CommandError, "ERR source and destination objects are the same") @client.select(0) expect(@client.get("key1")).to eq("1") end it "should scan all keys in the database" do 100.times do |x| @client.set("key#{x}", "#{x}") end cursor = 0 all_keys = [] loop { cursor, keys = @client.scan(cursor) all_keys += keys break if cursor == "0" } expect(all_keys.uniq.size).to eq(100) expect(all_keys[0]).to match(/key\d+/) end it "should match keys to a pattern when scanning" do 50.times do |x| @client.set("key#{x}", "#{x}") end @client.set("miss_me", 1) @client.set("pass_me", 2) cursor = 0 all_keys = [] loop { cursor, keys = @client.scan(cursor, :match => "key*") all_keys += keys break if cursor == "0" } expect(all_keys.uniq.size).to eq(50) end it "should specify doing more work when scanning" do 100.times do |x| @client.set("key#{x}", "#{x}") end cursor, all_keys = @client.scan(cursor, :count => 100) expect(cursor).to eq("0") expect(all_keys.uniq.size).to eq(100) end context "with extended options" do it "uses ex option to set the expire time, in seconds" do ttl = 7 expect(@client.set("key1", "1", { :ex => ttl })).to eq("OK") expect(@client.ttl("key1")).to eq(ttl) end it "uses px option to set the expire time, in miliseconds" do ttl = 7000 expect(@client.set("key1", "1", { :px => ttl })).to eq("OK") expect(@client.ttl("key1")).to eq(ttl / 1000) end # Note that the redis-rb implementation will always give PX last. # Redis seems to process each expiration option and the last one wins. it "prefers the finer-grained PX expiration option over EX" do ttl_px = 6000 ttl_ex = 10 @client.set("key1", "1", { :px => ttl_px, :ex => ttl_ex }) expect(@client.ttl("key1")).to eq(ttl_px / 1000) @client.set("key1", "1", { :ex => ttl_ex, :px => ttl_px }) expect(@client.ttl("key1")).to eq(ttl_px / 1000) end it "uses nx option to only set the key if it does not already exist" do expect(@client.set("key1", "1", { :nx => true })).to eq(true) expect(@client.set("key1", "2", { :nx => true })).to eq(false) expect(@client.get("key1")).to eq("1") end it "uses xx option to only set the key if it already exists" do expect(@client.set("key2", "1", { :xx => true })).to eq(false) @client.set("key2", "2") expect(@client.set("key2", "1", { :xx => true })).to eq(true) expect(@client.get("key2")).to eq("1") end it "does not set the key if both xx and nx option are specified" do expect(@client.set("key2", "1", { :nx => true, :xx => true })).to eq(false) expect(@client.get("key2")).to be_nil end end describe "#dump" do it "returns nil for unknown key" do expect(@client.exists("key1")).to be false expect(@client.dump("key1")).to be nil end it "dumps a single known key successfully" do @client.set("key1", "zomgwtf") value = @client.dump("key1") expect(value).not_to eq nil expect(value).to be_a_kind_of(String) end it "errors with more than one argument" do expect { @client.dump("key1", "key2") }.to raise_error(ArgumentError) end end describe "#restore" do it "errors with a missing payload" do expect do @client.restore("key1", 0, nil) end.to raise_error(Redis::CommandError, "ERR DUMP payload version or checksum are wrong") end it "errors with an invalid payload" do expect do @client.restore("key1", 0, "zomgwtf not valid") end.to raise_error(Redis::CommandError, "ERR DUMP payload version or checksum are wrong") end describe "with a dumped value" do before do @client.set("key1", "original value") @dumped_value = @client.dump("key1") @client.del("key1") expect(@client.exists("key1")).to be false end it "restores to a new key successfully" do response = @client.restore("key1", 0, @dumped_value) expect(response).to eq "OK" end it "errors trying to restore to an existing key" do @client.set("key1", "something else") expect do @client.restore("key1", 0, @dumped_value) end.to raise_error(Redis::CommandError, "ERR Target key name is busy.") end it "restores successfully with a given expire time" do @client.restore("key2", 2000, @dumped_value) expect(@client.ttl("key2")).to eq 2 end it "restores a list successfully" do @client.lpush("key1", "val1") @client.lpush("key1", "val2") expect(@client.type("key1")).to eq "list" dumped_value = @client.dump("key1") response = @client.restore("key2", 0, dumped_value) expect(response).to eq "OK" expect(@client.type("key2")).to eq "list" end it "restores a set successfully" do @client.sadd("key1", "val1") @client.sadd("key1", "val2") expect(@client.type("key1")).to eq "set" dumped_value = @client.dump("key1") response = @client.restore("key2", 0, dumped_value) expect(response).to eq "OK" expect(@client.type("key2")).to eq "set" end end end describe "#psetex" do it "should set a key's time to live in milliseconds" do allow(Time).to receive(:now).and_return(1000) @client.psetex("key", 2200, "value") expect(@client.pttl("key")).to be_within(0.1).of(2200) end it "should return 'OK'" do expect(@client.psetex("key", 1000, "value")).to eq("OK") end end end end fakeredis-0.8.0/spec/memory_spec.rb0000644000004100000410000000451513627672425017330 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe FakeRedis do let(:redis) { Redis.new } def populate_keys_in_redis(num) num.times do |count| redis.set("key#{count}", count) end end describe '#write' do it 'should not send unexpected arguments' do expect { redis.write(['info', 'server']) }.not_to raise_error end end describe '#scan' do def result returned_keys = [] cursor = 0 loop do cursor, keys = redis.scan(cursor, match_arguments) returned_keys += keys break if cursor == '0' end returned_keys end before do populate_keys_in_redis(11) end context('when deleting') do it('preverves cursor') do cursor, keys = redis.scan('0') keys.each { |key| redis.del(key) } _, keys = redis.scan(cursor) expect(keys).to eq(%w(key10)) end end context 'with one namespace' do let(:match_arguments) { {} } it 'returns the expected array of keys' do expect(result).to match_array(redis.keys) end end context 'with multiple namespaces' do let(:namespaced_key) { 'test' } let(:match_arguments) { { match: namespaced_key } } before { redis.set(namespaced_key, 12) } it 'returns the expected array of keys' do expect(result).to match_array([namespaced_key]) end end end describe 'time' do before(:each) do allow(Time).to receive_message_chain(:now, :to_f).and_return(1397845595.5139461) end it 'is an array' do expect(redis.time).to be_an_instance_of(Array) end it 'has two elements' do expect(redis.time.count).to eql 2 end if fakeredis? it 'has the current time in seconds' do expect(redis.time.first).to eql 1397845595 end it 'has the current leftover microseconds' do expect(redis.time.last).to eql 513946 end end end describe '#client' do it 'returns OK when command is :setname' do expect(redis.client(:setname, 'my-client-01')).to eq 'OK' end it 'returns nil when command is :getname' do expect(redis.client(:getname)).to eq nil end it 'raises error for other commands' do expect { redis.write([:client, :wrong]) }.to raise_error(Redis::CommandError, "ERR unknown command 'wrong'") end end end fakeredis-0.8.0/spec/lists_spec.rb0000644000004100000410000002107313627672425017154 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "ListsMethods" do before(:each) do @client = Redis.new end it "should get an element from a list by its index" do @client.lpush("key1", "val1") @client.lpush("key1", "val2") expect(@client.lindex("key1", 0)).to eq("val2") expect(@client.lindex("key1", -1)).to eq("val1") expect(@client.lindex("key1", 3)).to eq(nil) end it "should insert an element before or after another element in a list" do @client.rpush("key1", "v1") @client.rpush("key1", "v3") @client.linsert("key1", :before, "v3", "v2") @client.linsert("key1", :after, "v3", 100) @client.linsert("key1", :before, 100, 99) expect(@client.lrange("key1", 0, -1)).to eq(["v1", "v2", "v3", "99", "100"]) end it "inserts with case-insensitive position token" do @client.rpush("key1", "v1") @client.rpush("key1", "v4") @client.linsert("key1", :BEFORE, "v4", "v2") @client.linsert("key1", "Before", "v4", "v3") @client.linsert("key1", :AFTER, "v4", "v5") @client.linsert("key1", "After", "v5", "v6") expect(@client.lrange("key1", 0, -1)).to eq(%w(v1 v2 v3 v4 v5 v6)) end it "should not insert if after/before index not found" do @client.rpush("key", "v1") expect(@client.linsert("key", :before, "unknown", "v2")).to eq(-1) expect(@client.linsert("key", :after, "unknown", "v3")).to eq(-1) expect(@client.lrange("key", 0, -1)).to eq(["v1"]) end it 'should allow multiple values to be added to a list in a single rpush' do @client.rpush('key1', [1, 2, 3]) expect(@client.lrange('key1', 0, -1)).to eq(['1', '2', '3']) end it 'should allow multiple values to be added to a list in a single lpush' do @client.lpush('key1', [1, 2, 3]) expect(@client.lrange('key1', 0, -1)).to eq(['3', '2', '1']) end it "should error if an invalid where argument is given" do @client.rpush("key1", "v1") @client.rpush("key1", "v3") expect { @client.linsert("key1", :invalid, "v3", "v2") }.to raise_error(Redis::CommandError, "ERR syntax error") end it "should get the length of a list" do @client.rpush("key1", "v1") @client.rpush("key1", "v2") expect(@client.llen("key1")).to eq(2) expect(@client.llen("key2")).to eq(0) end it "should remove and get the first element in a list" do @client.rpush("key1", "v1") @client.rpush("key1", "v2") @client.rpush("key1", "v3") expect(@client.lpop("key1")).to eq("v1") expect(@client.lrange("key1", 0, -1)).to eq(["v2", "v3"]) end it "should prepend a value to a list" do @client.rpush("key1", "v1") @client.rpush("key1", "v2") expect(@client.lrange("key1", 0, -1)).to eq(["v1", "v2"]) end it "should prepend a value to a list, only if the list exists" do @client.lpush("key1", "v1") @client.lpushx("key1", "v2") @client.lpushx("key2", "v3") expect(@client.lrange("key1", 0, -1)).to eq(["v2", "v1"]) expect(@client.llen("key2")).to eq(0) end it "should get a range of elements from a list" do @client.rpush("key1", "v1") @client.rpush("key1", "v2") @client.rpush("key1", "v3") expect(@client.lrange("key1", 1, -1)).to eq(["v2", "v3"]) expect(@client.lrange("key1", -999, -1)).to eq(["v1", "v2", "v3"]) end it "should remove elements from a list" do @client.rpush("key1", "v1") @client.rpush("key1", "v2") @client.rpush("key1", "v2") @client.rpush("key1", "v2") @client.rpush("key1", "v1") @client.rpush("key1", 42) expect(@client.lrem("key1", 1, "v1")).to eq(1) expect(@client.lrem("key1", -2, "v2")).to eq(2) expect(@client.lrem("key1", 0, 42)).to eq(1) expect(@client.llen("key1")).to eq(2) end it "should return 0 if key does not map to a list" do expect(@client.exists("nonexistant")).to eq(false) expect(@client.lrem("nonexistant", 0, "value")).to eq(0) end it "should remove list's key when list is empty" do @client.rpush("key1", "v1") @client.rpush("key1", "v2") @client.lrem("key1", 1, "v1") @client.lrem("key1", 1, "v2") expect(@client.exists("key1")).to eq(false) end it "should set the value of an element in a list by its index" do @client.rpush("key1", "one") @client.rpush("key1", "two") @client.rpush("key1", "three") @client.lset("key1", 0, "four") @client.lset("key1", -2, "five") @client.lset("key1", 2, 6) expect(@client.lrange("key1", 0, -1)).to eq(["four", "five", "6"]) expect { @client.lset("key1", 4, "seven") }.to raise_error(Redis::CommandError, "ERR index out of range") end it "should trim a list to the specified range" do @client.rpush("key1", "one") @client.rpush("key1", "two") @client.rpush("key1", "three") expect(@client.ltrim("key1", 1, -1)).to eq("OK") expect(@client.lrange("key1", 0, -1)).to eq(["two", "three"]) end context "when the list is smaller than the requested trim" do before { @client.rpush("listOfOne", "one") } context "trimming with a negative start (specifying a max)" do before { @client.ltrim("listOfOne", -5, -1) } it "returns the unmodified list" do expect(@client.lrange("listOfOne", 0, -1)).to eq(["one"]) end end end context "when the list is larger than the requested trim" do before do @client.rpush("maxTest", "one") @client.rpush("maxTest", "two") @client.rpush("maxTest", "three") @client.rpush("maxTest", "four") @client.rpush("maxTest", "five") @client.rpush("maxTest", "six") end context "trimming with a negative start (specifying a max)" do before { @client.ltrim("maxTest", -5, -1) } it "should trim a list to the specified maximum size" do expect(@client.lrange("maxTest", 0, -1)).to eq(["two","three", "four", "five", "six"]) end end end it "should remove and return the last element in a list" do @client.rpush("key1", "one") @client.rpush("key1", "two") @client.rpush("key1", "three") expect(@client.rpop("key1")).to eq("three") expect(@client.lrange("key1", 0, -1)).to eq(["one", "two"]) end it "rpoplpush should remove the last element in a list, append it to another list and return it" do @client.rpush("key1", "one") @client.rpush("key1", "two") @client.rpush("key1", "three") expect(@client.rpoplpush("key1", "key2")).to eq("three") expect(@client.lrange("key1", 0, -1)).to eq(["one", "two"]) expect(@client.lrange("key2", 0, -1)).to eq(["three"]) end it "brpoplpush should remove the last element in a list, append it to another list and return it" do @client.rpush("key1", "one") @client.rpush("key1", "two") @client.rpush("key1", "three") expect(@client.brpoplpush("key1", "key2")).to eq("three") expect(@client.lrange("key1", 0, -1)).to eq(["one", "two"]) expect(@client.lrange("key2", 0, -1)).to eq(["three"]) end context 'when the source list is empty' do it 'rpoplpush does not add anything to the destination list' do @client.rpoplpush("source", "destination") expect(@client.lrange("destination", 0, -1)).to eq([]) end it 'brpoplpush does not add anything to the destination list' do expect(@client.brpoplpush("source", "destination")).to be_nil expect(@client.lrange("destination", 0, -1)).to eq([]) end end it "should append a value to a list" do @client.rpush("key1", "one") @client.rpush("key1", "two") expect(@client.lrange("key1", 0, -1)).to eq(["one", "two"]) end it "should append a value to a list, only if the list exists" do @client.rpush("key1", "one") @client.rpushx("key1", "two") @client.rpushx("key2", "two") expect(@client.lrange("key1", 0, -1)).to eq(["one", "two"]) expect(@client.lrange("key2", 0, -1)).to eq([]) end it 'should not allow pushing empty list of objects' do expect { @client.lpush("key1", []) }.to raise_error(Redis::CommandError, /lpush[^x]/) expect { @client.lpush("key1", 1); @client.lpushx("key1", []) }.to raise_error(Redis::CommandError, /lpushx/) expect { @client.rpush("key1", []) }.to raise_error(Redis::CommandError, /rpush[^x]/) expect { @client.rpush("key1", 1); @client.rpushx("key1", []) }.to raise_error(Redis::CommandError, /rpushx/) end end end fakeredis-0.8.0/spec/spec_helper.rb0000644000004100000410000000062013627672425017270 0ustar www-datawww-datarequire 'rspec' $LOAD_PATH.unshift(File.join(__dir__, '..', 'lib')) $LOAD_PATH.unshift(File.join(__dir__, '..')) Dir['spec/support/**/*.rb'].each { |f| require f } require 'fakeredis' require "fakeredis/rspec" RSpec.configure do |config| # Enable memory adapter config.before(:each) { FakeRedis.enable } config.backtrace_exclusion_patterns = [] end def fakeredis? FakeRedis.enabled? end fakeredis-0.8.0/spec/support/0000755000004100000410000000000013627672425016170 5ustar www-datawww-datafakeredis-0.8.0/spec/support/shared_examples/0000755000004100000410000000000013627672425021334 5ustar www-datawww-datafakeredis-0.8.0/spec/support/shared_examples/bitwise_operation.rb0000644000004100000410000000366313627672425025417 0ustar www-datawww-datashared_examples_for "a bitwise operation" do |operator| it 'raises an argument error when not passed any source keys' do expect { @client.bitop(operator, "destkey") }.to raise_error(Redis::CommandError) end it "should not create destination key if nothing found" do expect(@client.bitop(operator, "dest1", "nothing_here1")).to eq(0) expect(@client.exists("dest1")).to eq(false) end it "should accept operator as a case-insensitive symbol" do @client.set("key1", "foobar") @client.bitop(operator.to_s.downcase.to_sym, "dest1", "key1") @client.bitop(operator.to_s.upcase.to_sym, "dest2", "key1") expect(@client.get("dest1")).to eq("foobar") expect(@client.get("dest2")).to eq("foobar") end it "should accept operator as a case-insensitive string" do @client.set("key1", "foobar") @client.bitop(operator.to_s.downcase, "dest1", "key1") @client.bitop(operator.to_s.upcase, "dest2", "key1") expect(@client.get("dest1")).to eq("foobar") expect(@client.get("dest2")).to eq("foobar") end it "should copy original string for single key" do @client.set("key1", "foobar") @client.bitop(operator, "dest1", "key1") expect(@client.get("dest1")).to eq("foobar") end it "should copy original string for single key" do @client.set("key1", "foobar") @client.bitop(operator, "dest1", "key1") expect(@client.get("dest1")).to eq("foobar") end it "should return length of the string stored in the destination key" do @client.set("key1", "foobar") @client.set("key2", "baz") expect(@client.bitop(operator, "dest1", "key1")).to eq(6) expect(@client.bitop(operator, "dest2", "key2")).to eq(3) end it "should overwrite previous value with new one" do @client.set("key1", "foobar") @client.set("key2", "baz") @client.bitop(operator, "dest1", "key1") @client.bitop(operator, "dest1", "key2") expect(@client.get("dest1")).to eq("baz") end end fakeredis-0.8.0/spec/support/shared_examples/sortable.rb0000644000004100000410000000463313627672425023502 0ustar www-datawww-datashared_examples_for "a sortable" do it 'returns empty array on nil' do expect(@client.sort(nil)).to eq([]) end context 'ordering' do it 'orders ascending by default' do expect(@client.sort(@key)).to eq(['1', '2']) end it 'orders by ascending when specified' do expect(@client.sort(@key, :order => "ASC")).to eq(['1', '2']) end it 'orders by descending when specified' do expect(@client.sort(@key, :order => "DESC")).to eq(['2', '1']) end it "orders by ascending when alpha is specified" do expect(@client.sort(@key, :order => "ALPHA")).to eq(["1", "2"]) end end context 'projections' do it 'projects element when :get is "#"' do expect(@client.sort(@key, :get => '#')).to eq(['1', '2']) end it 'projects through a key pattern' do expect(@client.sort(@key, :get => 'fake-redis-test:values_*')).to eq(['a', 'b']) end it 'projects through a key pattern and reflects element' do expect(@client.sort(@key, :get => ['#', 'fake-redis-test:values_*'])).to eq([['1', 'a'], ['2', 'b']]) end it 'projects through a hash key pattern' do expect(@client.sort(@key, :get => 'fake-redis-test:hash_*->key')).to eq(['x', 'y']) end end context 'weights' do it 'weights by projecting through a key pattern' do expect(@client.sort(@key, :by => "fake-redis-test:weight_*")).to eq(['2', '1']) end it 'weights by projecting through a key pattern and a specific order' do expect(@client.sort(@key, :order => "DESC", :by => "fake-redis-test:weight_*")).to eq(['1', '2']) end end context 'limit' do it 'only returns requested window in the enumerable' do expect(@client.sort(@key, :limit => [0, 1])).to eq(['1']) end it 'returns an empty array if the offset if more than the length of the list' do expect(@client.sort(@key, :limit => [3, 1])).to eq([]) end end context 'store' do it 'stores into another key' do expect(@client.sort(@key, :store => "fake-redis-test:some_bucket")).to eq(2) expect(@client.lrange("fake-redis-test:some_bucket", 0, -1)).to eq(['1', '2']) end it "stores into another key with other options specified" do expect(@client.sort(@key, :store => "fake-redis-test:some_bucket", :by => "fake-redis-test:weight_*")).to eq(2) expect(@client.lrange("fake-redis-test:some_bucket", 0, -1)).to eq(['2', '1']) end end end fakeredis-0.8.0/spec/upcase_method_name_spec.rb0000644000004100000410000000055613627672425021641 0ustar www-datawww-datarequire 'spec_helper' module FakeRedis describe "UPCASE method name will call downcase method" do before do @client = Redis.new end it "#ZCOUNT" do expect(@client.ZCOUNT("key", 2, 3)).to eq(@client.zcount("key", 2, 3)) end it "#ZSCORE" do expect(@client.ZSCORE("key", 2)).to eq(@client.zscore("key", 2)) end end end fakeredis-0.8.0/.gitignore0000644000004100000410000000011513627672425015507 0ustar www-datawww-data*.gem .bundle Gemfile.lock pkg/* .rvmrc *.rbc .ruby-version .ruby-gemset bin fakeredis-0.8.0/LICENSE0000644000004100000410000000205313627672425014527 0ustar www-datawww-dataCopyright (c) 2011-2018 Guillermo Iguaran Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fakeredis-0.8.0/Rakefile0000644000004100000410000000110413627672425015163 0ustar www-datawww-datarequire 'bundler' Bundler::GemHelper.install_tasks $:.push File.expand_path("../lib", __FILE__) require "fakeredis/version" Bundler::GemHelper.install_tasks require 'rspec/core' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = FileList['spec/**/*_spec.rb'] spec.ruby_opts="-w" end task :default => :spec require 'rdoc/task' Rake::RDocTask.new do |rdoc| version = FakeRedis::VERSION rdoc.rdoc_dir = 'rdoc' rdoc.title = "fakeredis #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end fakeredis-0.8.0/lib/0000755000004100000410000000000013627672425014270 5ustar www-datawww-datafakeredis-0.8.0/lib/fakeredis.rb0000644000004100000410000000110413627672425016546 0ustar www-datawww-datarequire 'redis' require 'redis/connection/memory' module FakeRedis Redis = ::Redis def self.enable Redis::Connection.drivers << Redis::Connection::Memory unless enabled? end def self.enabled? Redis::Connection.drivers.last == Redis::Connection::Memory end def self.disable Redis::Connection.drivers.delete_if {|driver| Redis::Connection::Memory == driver } end def self.disabling return yield unless enabled? disable yield enable end def self.enabling return yield if enabled? enable yield disable end end fakeredis-0.8.0/lib/fakeredis/0000755000004100000410000000000013627672425016225 5ustar www-datawww-datafakeredis-0.8.0/lib/fakeredis/sorted_set_store.rb0000644000004100000410000000374313627672425022150 0ustar www-datawww-datamodule FakeRedis class SortedSetStore attr_accessor :data, :weights, :aggregate, :keys def initialize params, data self.data = data self.weights = params.weights self.aggregate = params.aggregate self.keys = params.keys end def hashes @hashes ||= keys.map do |src| case data[src] when ::Set # Every value has a score of 1 Hash[data[src].map {|k,v| [k, 1]}] when Hash data[src] else {} end end end # Apply the weightings to the hashes def computed_values unless defined?(@computed_values) && @computed_values # Do nothing if all weights are 1, as n * 1 is n @computed_values = hashes if weights.all? {|weight| weight == 1 } # Otherwise, multiply the values in each hash by that hash's weighting @computed_values ||= hashes.each_with_index.map do |hash, index| weight = weights[index] Hash[hash.map {|k, v| [k, (v * weight)]}] end end @computed_values end def aggregate_sum out selected_keys.each do |key| out[key] = computed_values.inject(0) do |n, hash| n + (hash[key] || 0) end end end def aggregate_min out selected_keys.each do |key| out[key] = computed_values.map {|h| h[key] }.compact.min end end def aggregate_max out selected_keys.each do |key| out[key] = computed_values.map {|h| h[key] }.compact.max end end def selected_keys raise NotImplemented, "subclass needs to implement #selected_keys" end def call ZSet.new.tap {|out| send("aggregate_#{aggregate}", out) } end end class SortedSetIntersectStore < SortedSetStore def selected_keys @values ||= hashes.map(&:keys).reduce(:&) end end class SortedSetUnionStore < SortedSetStore def selected_keys @values ||= hashes.map(&:keys).flatten.uniq end end end fakeredis-0.8.0/lib/fakeredis/transaction_commands.rb0000644000004100000410000000320313627672425022756 0ustar www-datawww-datamodule FakeRedis TRANSACTION_COMMANDS = [:discard, :exec, :multi, :watch, :unwatch, :client] module TransactionCommands def self.included(klass) klass.class_eval do def self.queued_commands @queued_commands ||= Hash.new {|h,k| h[k] = [] } end def self.in_multi @in_multi ||= Hash.new{|h,k| h[k] = false} end def queued_commands self.class.queued_commands[database_instance_key] end def queued_commands=(cmds) self.class.queued_commands[database_instance_key] = cmds end def in_multi self.class.in_multi[database_instance_key] end def in_multi=(multi_state) self.class.in_multi[database_instance_key] = multi_state end end end def discard unless in_multi raise Redis::CommandError, "ERR DISCARD without MULTI" end self.in_multi = false self.queued_commands = [] 'OK' end def exec unless in_multi raise Redis::CommandError, "ERR EXEC without MULTI" end responses = queued_commands.map do |cmd| begin send(*cmd) rescue => e e end end self.queued_commands = [] # reset queued_commands self.in_multi = false # reset in_multi state responses end def multi if in_multi raise Redis::CommandError, "ERR MULTI calls can not be nested" end self.in_multi = true yield(self) if block_given? "OK" end def watch(*_) "OK" end def unwatch "OK" end end end fakeredis-0.8.0/lib/fakeredis/version.rb0000644000004100000410000000005113627672425020233 0ustar www-datawww-datamodule FakeRedis VERSION = "0.8.0" end fakeredis-0.8.0/lib/fakeredis/sort_method.rb0000644000004100000410000000627213627672425021110 0ustar www-datawww-data# Codes are mostly referenced from MockRedis' implementation. module FakeRedis module SortMethod def sort(key, *redis_options_array) return [] unless key return [] if type(key) == 'none' unless %w(list set zset).include? type(key) raise Redis::CommandError.new("WRONGTYPE Operation against a key holding the wrong kind of value") end # redis_options is an array of format [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] # Lets nibble it back into a hash options = extract_options_from(redis_options_array) # And now to actually do the work of this method projected = project(data[key], options[:by], options[:get]) sorted = sort_by(projected, options[:order]) sliced = slice(sorted, options[:limit]) # We have to flatten it down as redis-rb adds back the array to the return value result = sliced.flatten(1) options[:store] ? rpush(options[:store], sliced) : result end private ASCENDING_SORT = Proc.new { |a, b| a.first <=> b.first } DESCENDING_SORT = Proc.new { |a, b| b.first <=> a.first } def extract_options_from(options_array) # Defaults options = { :limit => [], :order => "ASC", :get => [] } if options_array.first == "BY" options_array.shift options[:by] = options_array.shift end if options_array.first == "LIMIT" options_array.shift options[:limit] = [options_array.shift, options_array.shift] end while options_array.first == "GET" options_array.shift options[:get] << options_array.shift end if %w(ASC DESC ALPHA).include?(options_array.first) options[:order] = options_array.shift options[:order] = "ASC" if options[:order] == "ALPHA" end if options_array.first == "STORE" options_array.shift options[:store] = options_array.shift end options end def project(enumerable, by, get_patterns) enumerable.map do |*elements| element = elements.flatten.first weight = by ? lookup_from_pattern(by, element) : element value = element if get_patterns.length > 0 value = get_patterns.map do |pattern| pattern == "#" ? element : lookup_from_pattern(pattern, element) end value = value.first if value.length == 1 end [weight, value] end end def sort_by(projected, direction) sorter = case direction.upcase when "DESC" DESCENDING_SORT when "ASC", "ALPHA" ASCENDING_SORT else raise "Invalid direction '#{direction}'" end projected.sort(&sorter).map(&:last) end def slice(sorted, limit) skip = limit.first || 0 take = limit.last || sorted.length sorted[skip...(skip + take)] || [] end def lookup_from_pattern(pattern, element) key = pattern.sub('*', element) if (hash_parts = key.split('->')).length > 1 hget hash_parts.first, hash_parts.last else get key end end end end fakeredis-0.8.0/lib/fakeredis/zset.rb0000644000004100000410000000154713627672425017546 0ustar www-datawww-datamodule FakeRedis class ZSet < Hash def []=(key, val) super(key, _floatify(val)) end def identical_scores? values.uniq.size == 1 end # Increments the value of key by val def increment(key, val) self[key] += _floatify(val) end def select_by_score min, max min = _floatify(min, true) max = _floatify(max, false) select {|_,v| v >= min && v <= max } end private # Originally lifted from redis-rb def _floatify(str, increment = true) if (( inf = str.to_s.match(/^([+-])?inf/i) )) (inf[1] == "-" ? -1.0 : 1.0) / 0.0 elsif (( number = str.to_s.match(/^\((\d+)/i) )) number[1].to_i + (increment ? 1 : -1) else Float str.to_s end rescue ArgumentError raise Redis::CommandError, "ERR value is not a valid float" end end end fakeredis-0.8.0/lib/fakeredis/sorted_set_argument_handler.rb0000644000004100000410000000475013627672425024332 0ustar www-datawww-datamodule FakeRedis # Takes in the variable length array of arguments for a zinterstore/zunionstore method # and parses them into a few attributes for the method to access. # # Handles throwing errors for various scenarios (matches redis): # * Custom weights specified, but not enough or too many given # * Invalid aggregate value given # * Multiple aggregate values given class SortedSetArgumentHandler # [Symbol] The aggregate method to use for the output values. One of %w(sum min max) expected attr_reader :aggregate # [Integer] Number of keys in the argument list attr_accessor :number_of_keys # [Array] The actual keys in the argument list attr_accessor :keys # [Array] integers for weighting the values of each key - one number per key expected attr_accessor :weights # Used internally attr_accessor :type # Expects all the argments for the method to be passed as an array def initialize args # Pull out known lengths of data self.number_of_keys = args.shift self.keys = args.shift(number_of_keys) # Handle the variable lengths of data (WEIGHTS/AGGREGATE) args.inject(self) {|handler, item| handler.handle(item) } # Defaults for unspecified things self.weights ||= Array.new(number_of_keys) { 1 } self.aggregate ||= :sum # Validate values raise(Redis::CommandError, "ERR syntax error") unless weights.size == number_of_keys raise(Redis::CommandError, "ERR syntax error") unless [:min, :max, :sum].include?(aggregate) end # Only allows assigning a value *once* - raises Redis::CommandError if a second is given def aggregate=(str) raise(Redis::CommandError, "ERR syntax error") if (defined?(@aggregate) && @aggregate) @aggregate = str.to_s.downcase.to_sym end # Decides how to handle an item, depending on where we are in the arguments def handle(item) case item when "WEIGHTS" self.type = :weights self.weights = [] when "AGGREGATE" self.type = :aggregate when nil # This should never be called, raise a syntax error if we manage to hit it raise(Redis::CommandError, "ERR syntax error") else send "handle_#{type}", item end self end def handle_weights(item) self.weights << item end def handle_aggregate(item) self.aggregate = item end def inject_block lambda { |handler, item| handler.handle(item) } end end end fakeredis-0.8.0/lib/fakeredis/geo_commands.rb0000644000004100000410000000761413627672425021215 0ustar www-datawww-datarequire "fakeredis/geo_set" module FakeRedis module GeoCommands DISTANCE_UNITS = { "m" => 1, "km" => 1000, "ft" => 0.3048, "mi" => 1609.34 } REDIS_DOUBLE_PRECISION = 4 REDIS_GEOHASH_SIZE = 10 def geoadd(key, *members) raise_argument_error("geoadd") if members.empty? || members.size % 3 != 0 set = (data[key] ||= GeoSet.new) prev_size = set.size members.each_slice(3) do |member| set.add(*member) end set.size - prev_size end def geodist(key, member1, member2, unit = "m") unit = unit.to_s raise_command_error("ERR unsupported unit provided. please use #{DISTANCE_UNITS.keys.join(', ')}") unless DISTANCE_UNITS.include?(unit) set = (data[key] || GeoSet.new) point1 = set.get(member1) point2 = set.get(member2) if point1 && point2 distance = point1.distance_to(point2) distance_in_units = distance / DISTANCE_UNITS[unit] distance_in_units.round(REDIS_DOUBLE_PRECISION).to_s end end def geohash(key, member) members = Array(member) raise_argument_error("geohash") if members.empty? set = (data[key] || GeoSet.new) members.map do |member| point = set.get(member) point.geohash(REDIS_GEOHASH_SIZE) if point end end def geopos(key, member) return nil unless data[key] members = Array(member) set = (data[key] || GeoSet.new) members.map do |member| point = set.get(member) [point.lon.to_s, point.lat.to_s] if point end end def georadius(*args) args = args.dup raise_argument_error("georadius") if args.size < 5 key, lon, lat, radius, unit, *rest = args raise_argument_error("georadius") unless DISTANCE_UNITS.has_key?(unit) radius *= DISTANCE_UNITS[unit] set = (data[key] || GeoSet.new) center = GeoSet::Point.new(lon, lat, nil) do_georadius(set, center, radius, unit, rest) end def georadiusbymember(*args) args = args.dup raise_argument_error("georadiusbymember") if args.size < 4 key, member, radius, unit, *rest = args raise_argument_error("georadiusbymember") unless DISTANCE_UNITS.has_key?(unit) radius *= DISTANCE_UNITS[unit] set = (data[key] || GeoSet.new) center = set.get(member) raise_command_error("ERR could not decode requested zset member") unless center do_georadius(set, center, radius, unit, args) end private def do_georadius(set, center, radius, unit, args) points = set.points_within_radius(center, radius) options = georadius_options(args) if options[:asc] points.sort_by! { |p| p.distance_to(center) } elsif options[:desc] points.sort_by! { |p| -p.distance_to(center) } end points = points.take(options[:count]) if options[:count] extras = options[:extras] return points.map(&:name) if extras.empty? points.map do |point| member = [point.name] extras.each do |extra| case extra when "WITHCOORD" member << [point.lon.to_s, point.lat.to_s] when "WITHDIST" distance = point.distance_to(center) distance_in_units = distance / DISTANCE_UNITS[unit] member << distance_in_units.round(REDIS_DOUBLE_PRECISION).to_s when "WITHHASH" member << point.geohash(REDIS_GEOHASH_SIZE) end end member end end def georadius_options(args) options = {} args = args.map { |arg| arg.to_s.upcase } if idx = args.index("COUNT") options[:count] = Integer(args[idx + 1]) end options[:asc] = true if args.include?("ASC") options[:desc] = true if args.include?("DESC") extras = args & ["WITHCOORD", "WITHDIST", "WITHHASH"] options[:extras] = extras options end end end fakeredis-0.8.0/lib/fakeredis/minitest.rb0000644000004100000410000000075313627672425020413 0ustar www-datawww-data# Require this either in your Gemfile or in your minitest configuration. # Examples: # # # Gemfile # group :test do # gem 'minitest' # gem 'fakeredis', :require => 'fakeredis/minitest' # end # # # test/test_helper.rb (or test/minitest_config.rb) # require 'fakeredis/minitest' require 'fakeredis' module FakeRedis module Minitest def setup Redis::Connection::Memory.reset_all_databases super end ::Minitest::Test.send(:include, self) end end fakeredis-0.8.0/lib/fakeredis/bitop_command.rb0000644000004100000410000000246313627672425021372 0ustar www-datawww-datamodule FakeRedis module BitopCommand BIT_OPERATORS = { 'or' => :|, 'and' => :&, 'xor' => :'^', 'not' => :~, } def bitop(operation, destkey, *keys) if result = apply(operator(operation), keys) set(destkey, result) result.length else 0 end rescue ArgumentError => _ raise_argument_error('bitop') end private def operator(operation) BIT_OPERATORS[operation.to_s.downcase] end def apply(operator, keys) case operator when :~ raise ArgumentError if keys.count != 1 bitwise_not(keys.first) when :&, :|, :'^' raise ArgumentError if keys.empty? bitwise_operation(operator, keys) else raise ArgumentError end end def bitwise_not(key) if value = get(keys.first) value.bytes.map { |byte| ~ byte }.pack('c*') end end def bitwise_operation(operation, keys) apply_onto, *values = keys.map { |key| get(key) }.reject(&:nil?) values.reduce(apply_onto) do |memo, value| shorter, longer = [memo, value].sort_by(&:length).map(&:bytes).map(&:to_a) longer.each_with_index.map do |byte, index| byte.send(operation, shorter[index] || 0) end.pack('c*') end end end end fakeredis-0.8.0/lib/fakeredis/expiring_hash.rb0000644000004100000410000000225313627672425021404 0ustar www-datawww-datamodule FakeRedis # Represents a normal hash with some additional expiration information # associated with each key class ExpiringHash < Hash attr_reader :expires def initialize(*) super @expires = {} end def [](key) key = normalize key delete(key) if expired?(key) super end def []=(key, val) key = normalize key expire(key) super end def delete(key) key = normalize key expire(key) super end def expire(key) key = normalize key expires.delete(key) end def expired?(key) key = normalize key expires.include?(key) && expires[key] <= Time.now end def key?(key) key = normalize key delete(key) if expired?(key) super end def values_at(*keys) keys = keys.map { |key| normalize(key) } keys.each { |key| delete(key) if expired?(key) } super end def keys super.select do |key| key = normalize(key) if expired?(key) delete(key) false else true end end end def normalize key key.to_s end end end fakeredis-0.8.0/lib/fakeredis/command_executor.rb0000644000004100000410000000111413627672425022103 0ustar www-datawww-datamodule FakeRedis module CommandExecutor def write(command) meffod = command[0].to_s.downcase.to_sym args = command[1..-1] if in_multi && !(TRANSACTION_COMMANDS.include? meffod) # queue commands queued_commands << [meffod, *args] reply = 'QUEUED' elsif respond_to?(meffod) && method(meffod).arity.zero? reply = send(meffod) elsif respond_to?(meffod) reply = send(meffod, *args) else raise Redis::CommandError, "ERR unknown command '#{meffod}'" end replies << reply nil end end end fakeredis-0.8.0/lib/fakeredis/geo_set.rb0000644000004100000410000000355213627672425020204 0ustar www-datawww-datamodule FakeRedis class GeoSet class Point BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz" # (geohash-specific) Base32 map EARTH_RADIUS_IN_M = 6_378_100.0 attr_reader :lon, :lat, :name def initialize(lon, lat, name) @lon = Float(lon) @lat = Float(lat) @name = name end def geohash(precision = 10) latlon = [@lat, @lon] ranges = [[-90.0, 90.0], [-180.0, 180.0]] coordinate = 1 (0...precision).map do index = 0 # index into base32 map 5.times do |bit| mid = (ranges[coordinate][0] + ranges[coordinate][1]) / 2 if latlon[coordinate] >= mid index = index * 2 + 1 ranges[coordinate][0] = mid else index *= 2 ranges[coordinate][1] = mid end coordinate ^= 1 end BASE32[index] end.join end def distance_to(other) lat1 = deg_to_rad(@lat) lon1 = deg_to_rad(@lon) lat2 = deg_to_rad(other.lat) lon2 = deg_to_rad(other.lon) haversine_distance(lat1, lon1, lat2, lon2) end private def deg_to_rad(deg) deg * Math::PI / 180.0 end def haversine_distance(lat1, lon1, lat2, lon2) h = Math.sin((lat2 - lat1) / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin((lon2 - lon1) / 2) ** 2 2 * EARTH_RADIUS_IN_M * Math.asin(Math.sqrt(h)) end end def initialize @points = {} end def size @points.size end def add(lon, lat, name) @points[name] = Point.new(lon, lat, name) end def get(name) @points[name] end def points_within_radius(center, radius) @points.values.select do |point| point.distance_to(center) <= radius end end end end fakeredis-0.8.0/lib/fakeredis/rspec.rb0000644000004100000410000000071013627672425017664 0ustar www-datawww-data# Require this either in your Gemfile or in RSpec's # support scripts. Examples: # # # Gemfile # group :test do # gem "rspec" # gem "fakeredis", :require => "fakeredis/rspec" # end # # # spec/support/fakeredis.rb # require 'fakeredis/rspec' # require 'rspec/core' require 'fakeredis' RSpec.configure do |c| c.before do Redis::Connection::Memory.reset_all_databases Redis::Connection::Memory.reset_all_channels end end fakeredis-0.8.0/lib/redis/0000755000004100000410000000000013627672425015376 5ustar www-datawww-datafakeredis-0.8.0/lib/redis/connection/0000755000004100000410000000000013627672425017535 5ustar www-datawww-datafakeredis-0.8.0/lib/redis/connection/memory.rb0000644000004100000410000012537113627672425021403 0ustar www-datawww-datarequire 'set' require 'redis/connection/registry' require 'redis/connection/command_helper' require "fakeredis/command_executor" require "fakeredis/expiring_hash" require "fakeredis/sort_method" require "fakeredis/sorted_set_argument_handler" require "fakeredis/sorted_set_store" require "fakeredis/transaction_commands" require "fakeredis/zset" require "fakeredis/bitop_command" require "fakeredis/geo_commands" require "fakeredis/version" class Redis module Connection DEFAULT_REDIS_VERSION = '3.3.5' class Memory include Redis::Connection::CommandHelper include FakeRedis include SortMethod include TransactionCommands include BitopCommand include GeoCommands include CommandExecutor attr_accessor :options # Tracks all databases for all instances across the current process. # We have to be able to handle two clients with the same host/port accessing # different databases at once without overwriting each other. So we store our # "data" outside the client instances, in this class level instance method. # Client instances access it with a key made up of their host/port, and then select # which DB out of the array of them they want. Allows the access we need. def self.databases @databases ||= Hash.new {|h,k| h[k] = [] } end # Used for resetting everything in specs def self.reset_all_databases @databases = nil end def self.channels @channels ||= Hash.new {|h,k| h[k] = [] } end def self.reset_all_channels @channels = nil end def self.connect(options = {}) new(options) end def initialize(options = {}) self.options = self.options ? self.options.merge(options) : options end def database_id @database_id ||= 0 end attr_writer :database_id def database_instance_key [options[:host], options[:port]].hash end def databases self.class.databases[database_instance_key] end def find_database id=database_id databases[id] ||= ExpiringHash.new end def data find_database end def replies @replies ||= [] end attr_writer :replies def connected? true end def connect_unix(path, timeout) end def disconnect end def client(command, _options = {}) case command when :setname then "OK" when :getname then nil else raise Redis::CommandError, "ERR unknown command '#{command}'" end end def timeout=(usecs) end def read replies.shift end def flushdb databases.delete_at(database_id) "OK" end def flushall self.class.databases[database_instance_key] = [] "OK" end def auth(password) "OK" end def select(index) data_type_check(index, Integer) self.database_id = index "OK" end def info { "redis_version" => options[:version] || DEFAULT_REDIS_VERSION, "connected_clients" => "1", "connected_slaves" => "0", "used_memory" => "3187", "changes_since_last_save" => "0", "last_save_time" => "1237655729", "total_connections_received" => "1", "total_commands_processed" => "1", "uptime_in_seconds" => "36000", "uptime_in_days" => 0 } end def monitor; end def save; end def bgsave; end def bgrewriteaof; end def evalsha; end def eval; end def move key, destination_id raise Redis::CommandError, "ERR source and destination objects are the same" if destination_id == database_id destination = find_database(destination_id) return false unless data.has_key?(key) return false if destination.has_key?(key) destination[key] = data.delete(key) true end def dump(key) return nil unless exists(key) value = data[key] Marshal.dump( value: value, version: FakeRedis::VERSION, # Redis includes the version, so we might as well ) end def restore(key, ttl, serialized_value) raise Redis::CommandError, "ERR Target key name is busy." if exists(key) raise Redis::CommandError, "ERR DUMP payload version or checksum are wrong" if serialized_value.nil? parsed_value = begin Marshal.load(serialized_value) rescue TypeError raise Redis::CommandError, "ERR DUMP payload version or checksum are wrong" end if parsed_value[:version] != FakeRedis::VERSION raise Redis::CommandError, "ERR DUMP payload version or checksum are wrong" end # We could figure out what type the key was and set it with the public API here, # or we could just assign the value. If we presume the serialized_value is only ever # a return value from `dump` then we've only been given something that was in # the internal data structure anyway. data[key] = parsed_value[:value] # Set a TTL if one has been passed ttl = ttl.to_i # Makes nil into 0 expire(key, ttl / 1000) unless ttl.zero? "OK" end def get(key) data_type_check(key, String) data[key] end def getbit(key, offset) return unless data[key] data[key].unpack('B*')[0].split("")[offset].to_i end def bitcount(key, start_index = 0, end_index = -1) return 0 unless data[key] data[key][start_index..end_index].unpack('B*')[0].count("1") end def bitpos(key, bit, start_index = 0, end_index = -1) value = data[key] || "" value[0..end_index].unpack('B*')[0].index(bit.to_s, start_index * 8) || -1 end def getrange(key, start, ending) return unless data[key] data[key][start..ending] end alias :substr :getrange def getset(key, value) data_type_check(key, String) data[key].tap do set(key, value) end end def mget(*keys) raise_argument_error('mget') if keys.empty? # We work with either an array, or list of arguments keys = keys.first if keys.size == 1 data.values_at(*keys) end def append(key, value) data[key] = (data[key] || "") data[key] = data[key] + value.to_s end def strlen(key) return unless data[key] data[key].size end def hgetall(key) data_type_check(key, Hash) data[key].to_a.flatten || {} end def hget(key, field) data_type_check(key, Hash) data[key] && data[key][field.to_s] end def hdel(key, field) data_type_check(key, Hash) return 0 unless data[key] if field.is_a?(Array) old_keys_count = data[key].size fields = field.map(&:to_s) data[key].delete_if { |k, v| fields.include? k } deleted = old_keys_count - data[key].size else field = field.to_s deleted = data[key].delete(field) ? 1 : 0 end remove_key_for_empty_collection(key) deleted end def hkeys(key) data_type_check(key, Hash) return [] if data[key].nil? data[key].keys end def hscan(key, start_cursor, *args) data_type_check(key, Hash) return ["0", []] unless data[key] match = "*" count = 10 if args.size.odd? raise_argument_error('hscan') end if idx = args.index("MATCH") match = args[idx + 1] end if idx = args.index("COUNT") count = args[idx + 1] end start_cursor = start_cursor.to_i cursor = start_cursor next_keys = [] if start_cursor + count >= data[key].length next_keys = (data[key].to_a)[start_cursor..-1] cursor = 0 else cursor = start_cursor + count next_keys = (data[key].to_a)[start_cursor..cursor-1] end filtered_next_keys = next_keys.select{|k,v| File.fnmatch(match, k)} result = filtered_next_keys.flatten.map(&:to_s) return ["#{cursor}", result] end def keys(pattern = "*") data.keys.select { |key| File.fnmatch(pattern, key) } end def randomkey data.keys[rand(dbsize)] end def echo(string) string end def ping "PONG" end def lastsave Time.now.to_i end def time microseconds = (Time.now.to_f * 1000000).to_i [ microseconds / 1000000, microseconds % 1000000 ] end def dbsize data.keys.count end def exists(key) data.key?(key) end def llen(key) data_type_check(key, Array) return 0 unless data[key] data[key].size end def lrange(key, startidx, endidx) data_type_check(key, Array) if data[key] # In Ruby when negative start index is out of range Array#slice returns # nil which is not the case for lrange in Redis. startidx = 0 if startidx < 0 && startidx.abs > data[key].size data[key][startidx..endidx] || [] else [] end end def ltrim(key, start, stop) data_type_check(key, Array) return unless data[key] # Example: we have a list of 3 elements and # we give it a ltrim list, -5, -1. This means # it should trim to a max of 5. Since 3 < 5 # we should not touch the list. This is consistent # with behavior of real Redis's ltrim with a negative # start argument. unless start < 0 && data[key].count < start.abs data[key] = data[key][start..stop] end "OK" end def lindex(key, index) data_type_check(key, Array) data[key] && data[key][index] end def linsert(key, where, pivot, value) data_type_check(key, Array) return unless data[key] value = value.to_s index = data[key].index(pivot.to_s) return -1 if index.nil? case where.to_s when /\Abefore\z/i then data[key].insert(index, value) when /\Aafter\z/i then data[key].insert(index + 1, value) else raise_syntax_error end end def lset(key, index, value) data_type_check(key, Array) return unless data[key] raise Redis::CommandError, "ERR index out of range" if index >= data[key].size data[key][index] = value.to_s end def lrem(key, count, value) data_type_check(key, Array) return 0 unless data[key] value = value.to_s old_size = data[key].size diff = if count == 0 data[key].delete(value) old_size - data[key].size else array = count > 0 ? data[key].dup : data[key].reverse count.abs.times{ array.delete_at(array.index(value) || array.length) } data[key] = count > 0 ? array.dup : array.reverse old_size - data[key].size end remove_key_for_empty_collection(key) diff end def rpush(key, value) raise_argument_error('rpush') if value.respond_to?(:each) && value.empty? data_type_check(key, Array) data[key] ||= [] [value].flatten.each do |val| data[key].push(val.to_s) end data[key].size end def rpushx(key, value) raise_argument_error('rpushx') if value.respond_to?(:each) && value.empty? data_type_check(key, Array) return unless data[key] rpush(key, value) end def lpush(key, value) raise_argument_error('lpush') if value.respond_to?(:each) && value.empty? data_type_check(key, Array) data[key] ||= [] [value].flatten.each do |val| data[key].unshift(val.to_s) end data[key].size end def lpushx(key, value) raise_argument_error('lpushx') if value.respond_to?(:each) && value.empty? data_type_check(key, Array) return unless data[key] lpush(key, value) end def rpop(key) data_type_check(key, Array) return unless data[key] data[key].pop end def brpop(keys, timeout=0) #todo threaded mode keys = Array(keys) keys.each do |key| if data[key] && data[key].size > 0 return [key, data[key].pop] end end sleep(timeout.to_f) nil end def rpoplpush(key1, key2) data_type_check(key1, Array) rpop(key1).tap do |elem| lpush(key2, elem) unless elem.nil? end end def brpoplpush(key1, key2, opts={}) data_type_check(key1, Array) _key, elem = brpop(key1) lpush(key2, elem) unless elem.nil? elem end def lpop(key) data_type_check(key, Array) return unless data[key] data[key].shift end def blpop(keys, timeout=0) #todo threaded mode keys = Array(keys) keys.each do |key| if data[key] && data[key].size > 0 return [key, data[key].shift] end end sleep(timeout.to_f) nil end def smembers(key) data_type_check(key, ::Set) return [] unless data[key] data[key].to_a.reverse end def sismember(key, value) data_type_check(key, ::Set) return false unless data[key] data[key].include?(value.to_s) end def sadd(key, value) data_type_check(key, ::Set) value = Array(value) raise_argument_error('sadd') if value.empty? result = if data[key] old_set = data[key].dup data[key].merge(value.map(&:to_s)) (data[key] - old_set).size else data[key] = ::Set.new(value.map(&:to_s)) data[key].size end # 0 = false, 1 = true, 2+ untouched return result == 1 if result < 2 result end def srem(key, value) data_type_check(key, ::Set) value = Array(value) raise_argument_error('srem') if value.empty? return false unless data[key] if value.is_a?(Array) old_size = data[key].size values = value.map(&:to_s) values.each { |v| data[key].delete(v) } deleted = old_size - data[key].size else deleted = !!data[key].delete?(value.to_s) end remove_key_for_empty_collection(key) deleted end def smove(source, destination, value) data_type_check(destination, ::Set) result = self.srem(source, value) self.sadd(destination, value) if result result end def spop(key, count = nil) data_type_check(key, ::Set) results = (count || 1).times.map do elem = srandmember(key) srem(key, elem) if elem elem end.compact count.nil? ? results.first : results end def scard(key) data_type_check(key, ::Set) return 0 unless data[key] data[key].size end def sinter(*keys) keys = keys[0] if flatten?(keys) raise_argument_error('sinter') if keys.empty? keys.each { |k| data_type_check(k, ::Set) } return ::Set.new if keys.any? { |k| data[k].nil? } keys = keys.map { |k| data[k] || ::Set.new } keys.inject do |set, key| set & key end.to_a end def sinterstore(destination, *keys) data_type_check(destination, ::Set) result = sinter(*keys) data[destination] = ::Set.new(result) end def sunion(*keys) keys = keys[0] if flatten?(keys) raise_argument_error('sunion') if keys.empty? keys.each { |k| data_type_check(k, ::Set) } keys = keys.map { |k| data[k] || ::Set.new } keys.inject(::Set.new) do |set, key| set | key end.to_a end def sunionstore(destination, *keys) data_type_check(destination, ::Set) result = sunion(*keys) data[destination] = ::Set.new(result) end def sdiff(key1, *keys) keys = keys[0] if flatten?(keys) [key1, *keys].each { |k| data_type_check(k, ::Set) } keys = keys.map { |k| data[k] || ::Set.new } keys.inject(data[key1] || Set.new) do |memo, set| memo - set end.to_a end def sdiffstore(destination, key1, *keys) data_type_check(destination, ::Set) result = sdiff(key1, *keys) data[destination] = ::Set.new(result) end def srandmember(key, number=nil) number.nil? ? srandmember_single(key) : srandmember_multiple(key, number) end def sscan(key, start_cursor, *args) data_type_check(key, ::Set) return ["0", []] unless data[key] match = "*" count = 10 if args.size.odd? raise_argument_error('sscan') end if idx = args.index("MATCH") match = args[idx + 1] end if idx = args.index("COUNT") count = args[idx + 1] end start_cursor = start_cursor.to_i cursor = start_cursor next_keys = [] if start_cursor + count >= data[key].length next_keys = (data[key].to_a)[start_cursor..-1] cursor = 0 else cursor = start_cursor + count next_keys = (data[key].to_a)[start_cursor..cursor-1] end filtered_next_keys = next_keys.select{ |k,v| File.fnmatch(match, k)} result = filtered_next_keys.flatten.map(&:to_s) return ["#{cursor}", result] end def del(*keys) delete_keys(keys, 'del') end def unlink(*keys) delete_keys(keys, 'unlink') end def setnx(key, value) if exists(key) 0 else set(key, value) 1 end end def rename(key, new_key) return unless data[key] data[new_key] = data[key] data.expires[new_key] = data.expires[key] if data.expires.include?(key) data.delete(key) end def renamenx(key, new_key) if exists(new_key) false else rename(key, new_key) true end end def expire(key, ttl) return 0 unless data[key] data.expires[key] = Time.now + ttl 1 end def pexpire(key, ttl) return 0 unless data[key] data.expires[key] = Time.now + (ttl / 1000.0) 1 end def ttl(key) if data.expires.include?(key) && (ttl = data.expires[key].to_i - Time.now.to_i) > 0 ttl else exists(key) ? -1 : -2 end end def pttl(key) if data.expires.include?(key) && (ttl = data.expires[key].to_f - Time.now.to_f) > 0 ttl * 1000 else exists(key) ? -1 : -2 end end def expireat(key, timestamp) data.expires[key] = Time.at(timestamp) true end def persist(key) !!data.expires.delete(key) end def hset(key, field, value) data_type_check(key, Hash) field = field.to_s if data[key] result = !data[key].include?(field) data[key][field] = value.to_s result ? 1 : 0 else data[key] = { field => value.to_s } 1 end end def hsetnx(key, field, value) data_type_check(key, Hash) field = field.to_s return false if data[key] && data[key][field] hset(key, field, value) end def hmset(key, *fields) # mapped_hmset gives us [[:k1, "v1", :k2, "v2"]] for `fields`. Fix that. fields = fields[0] if mapped_param?(fields) raise_argument_error('hmset') if fields.empty? is_list_of_arrays = fields.all?{|field| field.instance_of?(Array)} raise_argument_error('hmset') if fields.size.odd? and !is_list_of_arrays raise_argument_error('hmset') if is_list_of_arrays and !fields.all?{|field| field.length == 2} data_type_check(key, Hash) data[key] ||= {} if is_list_of_arrays fields.each do |pair| data[key][pair[0].to_s] = pair[1].to_s end else fields.each_slice(2) do |field| data[key][field[0].to_s] = field[1].to_s end end "OK" end def hmget(key, *fields) raise_argument_error('hmget') if fields.empty? || fields.flatten.empty? data_type_check(key, Hash) fields.flatten.map do |field| field = field.to_s if data[key] data[key][field] else nil end end end def hlen(key) data_type_check(key, Hash) return 0 unless data[key] data[key].size end def hstrlen(key, field) data_type_check(key, Hash) return 0 if data[key].nil? || data[key][field].nil? data[key][field].size end def hvals(key) data_type_check(key, Hash) return [] unless data[key] data[key].values end def hincrby(key, field, increment) data_type_check(key, Hash) field = field.to_s if data[key] data[key][field] = (data[key][field].to_i + increment.to_i).to_s else data[key] = { field => increment.to_s } end data[key][field].to_i end def hincrbyfloat(key, field, increment) data_type_check(key, Hash) field = field.to_s if data[key] data[key][field] = (data[key][field].to_f + increment.to_f).to_s else data[key] = { field => increment.to_s } end data[key][field] end def hexists(key, field) data_type_check(key, Hash) return false unless data[key] data[key].key?(field.to_s) end def sync ; end def set(key, value, *array_options) option_nx = array_options.delete("NX") option_xx = array_options.delete("XX") return nil if option_nx && option_xx return nil if option_nx && exists(key) return nil if option_xx && !exists(key) data[key] = value.to_s options = Hash[array_options.each_slice(2).to_a] ttl_in_seconds = options["EX"] if options["EX"] ttl_in_seconds = options["PX"] / 1000.0 if options["PX"] expire(key, ttl_in_seconds) if ttl_in_seconds "OK" end def setbit(key, offset, bit) old_val = data[key] ? data[key].unpack('B*')[0].split("") : [] size_increment = [((offset/8)+1)*8-old_val.length, 0].max old_val += Array.new(size_increment).map{"0"} original_val = old_val[offset].to_i old_val[offset] = bit.to_s new_val = "" old_val.each_slice(8){|b| new_val = new_val + b.join("").to_i(2).chr } data[key] = new_val original_val end def setex(key, seconds, value) data[key] = value.to_s expire(key, seconds) "OK" end def psetex(key, milliseconds, value) setex(key, milliseconds / 1000.0, value) end def setrange(key, offset, value) return unless data[key] s = data[key][offset,value.size] data[key][s] = value end def mset(*pairs) # Handle pairs for mapped_mset command pairs = pairs[0] if mapped_param?(pairs) raise_argument_error('mset') if pairs.empty? || pairs.size == 1 # We have to reply with a different error message here to be consistent with redis-rb 3.0.6 / redis-server 2.8.1 raise_argument_error("mset", "mset_odd") if pairs.size.odd? pairs.each_slice(2) do |pair| data[pair[0].to_s] = pair[1].to_s end "OK" end def msetnx(*pairs) # Handle pairs for mapped_msetnx command pairs = pairs[0] if mapped_param?(pairs) keys = [] pairs.each_with_index{|item, index| keys << item.to_s if index % 2 == 0} return false if keys.any?{|key| data.key?(key) } mset(*pairs) true end def incr(key) data.merge!({ key => (data[key].to_i + 1).to_s || "1"}) data[key].to_i end def incrby(key, by) data.merge!({ key => (data[key].to_i + by.to_i).to_s || by }) data[key].to_i end def incrbyfloat(key, by) data.merge!({ key => (data[key].to_f + by.to_f).to_s || by }) data[key] end def decr(key) data.merge!({ key => (data[key].to_i - 1).to_s || "-1"}) data[key].to_i end def decrby(key, by) data.merge!({ key => ((data[key].to_i - by.to_i) || (by.to_i * -1)).to_s }) data[key].to_i end def type(key) case data[key] when nil then "none" when String then "string" when ZSet then "zset" when Hash then "hash" when Array then "list" when ::Set then "set" end end def quit ; end def shutdown; end def slaveof(host, port) ; end def scan(start_cursor, *args) match = "*" count = 10 if idx = args.index("MATCH") match = args[idx + 1] end if idx = args.index("COUNT") count = args[idx + 1] end start_cursor = start_cursor.to_i data_type_check(start_cursor, Integer) cursor = start_cursor returned_keys = [] final_page = start_cursor + count >= keys(match).length if final_page previous_keys_been_deleted = (count >= keys(match).length) start_index = previous_keys_been_deleted ? 0 : cursor returned_keys = keys(match)[start_index..-1] cursor = 0 else end_index = start_cursor + (count - 1) returned_keys = keys(match)[start_cursor..end_index] cursor = start_cursor + count end return "#{cursor}", returned_keys end def zadd(key, *args) option_xx = args.delete("XX") option_nx = args.delete("NX") option_ch = args.delete("CH") option_incr = args.delete("INCR") if option_xx && option_nx raise_options_error("XX", "NX") end if option_incr && args.size > 2 raise_options_error("INCR") end if !args.first.is_a?(Array) if args.size < 2 raise_argument_error('zadd') elsif args.size.odd? raise_syntax_error end else unless args.all? {|pair| pair.size == 2 } raise_syntax_error end end data_type_check(key, ZSet) data[key] ||= ZSet.new # Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already args = args.each_slice(2).to_a unless args.first.is_a?(Array) changed = 0 exists = args.map(&:last).count { |el| !hexists(key, el.to_s) } args.each do |score, value| if option_nx && hexists(key, value.to_s) next end if option_xx && !hexists(key, value.to_s) exists -= 1 next end if option_incr data[key][value.to_s] ||= 0 return data[key].increment(value, score).to_s end if option_ch && data[key][value.to_s] != score changed += 1 end data[key][value.to_s] = score end if option_incr changed = changed.zero? ? nil : changed exists = exists.zero? ? nil : exists end option_ch ? changed : exists end def zrem(key, value) data_type_check(key, ZSet) values = Array(value) return 0 unless data[key] response = values.map do |v| data[key].delete(v.to_s) if data[key].has_key?(v.to_s) end.compact.size remove_key_for_empty_collection(key) response end def zpopmax(key, count = nil) data_type_check(key, ZSet) return [] unless data[key] sorted_members = sort_keys(data[key]) results = sorted_members.last(count || 1).reverse! results.each do |member| zrem(key, member.first) end count.nil? ? results.first : results.flatten end def zpopmin(key, count = nil) data_type_check(key, ZSet) return [] unless data[key] sorted_members = sort_keys(data[key]) results = sorted_members.first(count || 1) results.each do |member| zrem(key, member.first) end count.nil? ? results.first : results.flatten end def bzpopmax(*args) bzpop(:bzpopmax, args) end def bzpopmin(*args) bzpop(:bzpopmin, args) end def zcard(key) data_type_check(key, ZSet) data[key] ? data[key].size : 0 end def zscore(key, value) data_type_check(key, ZSet) value = data[key] && data[key][value.to_s] if value == Float::INFINITY "inf" elsif value == -Float::INFINITY "-inf" elsif value value.to_s end end def zcount(key, min, max) data_type_check(key, ZSet) return 0 unless data[key] data[key].select_by_score(min, max).size end def zincrby(key, num, value) data_type_check(key, ZSet) data[key] ||= ZSet.new data[key][value.to_s] ||= 0 data[key].increment(value.to_s, num) if num =~ /^\+?inf/ "inf" elsif num == "-inf" "-inf" else data[key][value.to_s].to_s end end def zrank(key, value) data_type_check(key, ZSet) z = data[key] return unless z z.keys.sort_by {|k| z[k] }.index(value.to_s) end def zrevrank(key, value) data_type_check(key, ZSet) z = data[key] return unless z z.keys.sort_by {|k| -z[k] }.index(value.to_s) end def zrange(key, start, stop, with_scores = nil) data_type_check(key, ZSet) return [] unless data[key] results = sort_keys(data[key]) # Select just the keys unless we want scores results = results.map(&:first) unless with_scores start = [start, -results.size].max (results[start..stop] || []).flatten.map(&:to_s) end def zrangebylex(key, start, stop, *opts) data_type_check(key, ZSet) return [] unless data[key] zset = data[key] sorted = if zset.identical_scores? zset.keys.sort { |x, y| x.to_s <=> y.to_s } else zset.keys end range = get_range start, stop, sorted.first, sorted.last filtered = [] sorted.each do |element| filtered << element if (range[0][:value]..range[1][:value]).cover?(element) end filtered.shift if filtered[0] == range[0][:value] && !range[0][:inclusive] filtered.pop if filtered.last == range[1][:value] && !range[1][:inclusive] limit = get_limit(opts, filtered) if limit filtered = filtered[limit[0]..-1].take(limit[1]) end filtered end def zrevrangebylex(key, start, stop, *args) zrangebylex(key, stop, start, args).reverse end def zrevrange(key, start, stop, with_scores = nil) data_type_check(key, ZSet) return [] unless data[key] if with_scores data[key].sort_by {|_,v| -v } else data[key].keys.sort_by {|k| -data[key][k] } end[start..stop].flatten.map(&:to_s) end def zrangebyscore(key, min, max, *opts) data_type_check(key, ZSet) return [] unless data[key] range = data[key].select_by_score(min, max) vals = if opts.include?('WITHSCORES') range.sort_by {|_,v| v } else range.keys.sort_by {|k| range[k] } end limit = get_limit(opts, vals) vals = vals[*limit] if limit vals.flatten.map(&:to_s) end def zrevrangebyscore(key, max, min, *opts) opts = opts.flatten data_type_check(key, ZSet) return [] unless data[key] range = data[key].select_by_score(min, max) vals = if opts.include?('WITHSCORES') range.sort_by {|_,v| -v } else range.keys.sort_by {|k| -range[k] } end limit = get_limit(opts, vals) vals = vals[*limit] if limit vals.flatten.map(&:to_s) end def zremrangebyscore(key, min, max) data_type_check(key, ZSet) return 0 unless data[key] range = data[key].select_by_score(min, max) range.each {|k,_| data[key].delete(k) } range.size end def zremrangebyrank(key, start, stop) data_type_check(key, ZSet) return 0 unless data[key] sorted_elements = data[key].sort_by { |k, v| v } start = sorted_elements.length if start > sorted_elements.length elements_to_delete = sorted_elements[start..stop] elements_to_delete.each { |elem, rank| data[key].delete(elem) } elements_to_delete.size end def zinterstore(out, *args) data_type_check(out, ZSet) args_handler = SortedSetArgumentHandler.new(args) data[out] = SortedSetIntersectStore.new(args_handler, data).call data[out].size end def zunionstore(out, *args) data_type_check(out, ZSet) args_handler = SortedSetArgumentHandler.new(args) data[out] = SortedSetUnionStore.new(args_handler, data).call data[out].size end def pfadd(key, member) data_type_check(key, Set) data[key] ||= Set.new previous_size = data[key].size data[key] |= Array(member) data[key].size != previous_size end def pfcount(*keys) keys = keys.flatten raise_argument_error("pfcount") if keys.empty? keys.each { |key| data_type_check(key, Set) } if keys.count == 1 (data[keys.first] || Set.new).size else union = keys.map { |key| data[key] }.compact.reduce(&:|) union.size end end def pfmerge(destination, *sources) sources.each { |source| data_type_check(source, Set) } union = sources.map { |source| data[source] || Set.new }.reduce(&:|) data[destination] = union "OK" end def subscribe(*channels) raise_argument_error('subscribe') if channels.empty?() #Create messages for all data from the channels channel_replies = channels.map do |channel| self.class.channels[channel].slice!(0..-1).map!{|v| ["message", channel, v]} end channel_replies.flatten!(1) channel_replies.compact!() #Put messages into the replies for the future channels.each_with_index do |channel,index| replies << ["subscribe", channel, index+1] end replies.push(*channel_replies) #Add unsubscribe message to stop blocking (see https://github.com/redis/redis-rb/blob/v3.2.1/lib/redis/subscribe.rb#L38) replies.push(self.unsubscribe()) replies.pop() #Last reply will be pushed back on end def psubscribe(*patterns) raise_argument_error('psubscribe') if patterns.empty?() #Create messages for all data from the channels channel_replies = self.class.channels.keys.map do |channel| pattern = patterns.find{|p| File.fnmatch(p, channel) } unless pattern.nil?() self.class.channels[channel].slice!(0..-1).map!{|v| ["pmessage", pattern, channel, v]} end end channel_replies.flatten!(1) channel_replies.compact!() #Put messages into the replies for the future patterns.each_with_index do |pattern,index| replies << ["psubscribe", pattern, index+1] end replies.push(*channel_replies) #Add unsubscribe to stop blocking replies.push(self.punsubscribe()) replies.pop() #Last reply will be pushed back on end def publish(channel, message) self.class.channels[channel] << message 0 #Just fake number of subscribers end def unsubscribe(*channels) if channels.empty?() replies << ["unsubscribe", nil, 0] else channels.each do |channel| replies << ["unsubscribe", channel, 0] end end replies.pop() #Last reply will be pushed back on end def punsubscribe(*patterns) if patterns.empty?() replies << ["punsubscribe", nil, 0] else patterns.each do |pattern| replies << ["punsubscribe", pattern, 0] end end replies.pop() #Last reply will be pushed back on end def zscan(key, start_cursor, *args) data_type_check(key, ZSet) return [] unless data[key] match = "*" count = 10 if args.size.odd? raise_argument_error('zscan') end if idx = args.index("MATCH") match = args[idx + 1] end if idx = args.index("COUNT") count = args[idx + 1] end start_cursor = start_cursor.to_i data_type_check(start_cursor, Integer) cursor = start_cursor next_keys = [] sorted_keys = sort_keys(data[key]) if start_cursor + count >= sorted_keys.length next_keys = sorted_keys.to_a.select { |k| File.fnmatch(match, k[0]) } [start_cursor..-1] cursor = 0 else cursor = start_cursor + count next_keys = sorted_keys.to_a.select { |k| File.fnmatch(match, k[0]) } [start_cursor..cursor-1] end return "#{cursor}", next_keys.flatten.map(&:to_s) end # Originally from redis-rb def zscan_each(key, *args, &block) data_type_check(key, ZSet) return [] unless data[key] return to_enum(:zscan_each, key, options) unless block_given? cursor = 0 loop do cursor, values = zscan(key, cursor, options) values.each(&block) break if cursor == "0" end end private def raise_argument_error(command, match_string=command) error_message = if %w(hmset mset_odd).include?(match_string.downcase) "ERR wrong number of arguments for #{command.upcase}" else "ERR wrong number of arguments for '#{command}' command" end raise Redis::CommandError, error_message end def raise_syntax_error raise Redis::CommandError, "ERR syntax error" end def raise_options_error(*options) if options.detect { |opt| opt.match(/incr/i) } error_message = "ERR INCR option supports a single increment-element pair" else error_message = "ERR #{options.join(" and ")} options at the same time are not compatible" end raise Redis::CommandError, error_message end def raise_command_error(message) raise Redis::CommandError, message end def delete_keys(keys, command) keys = keys.flatten(1) raise_argument_error(command) if keys.empty? old_count = data.keys.size keys.each do |key| data.delete(key) end old_count - data.keys.size end def remove_key_for_empty_collection(key) del(key) if data[key] && data[key].empty? end def data_type_check(key, klass) if data[key] && !data[key].is_a?(klass) raise Redis::CommandError.new("WRONGTYPE Operation against a key holding the wrong kind of value") end end def get_range(start, stop, min = -Float::INFINITY, max = Float::INFINITY) range_options = [] [start, stop].each do |value| case value[0] when "-" range_options << { value: min, inclusive: true } when "+" range_options << { value: max, inclusive: true } when "[" range_options << { value: value[1..-1], inclusive: true } when "(" range_options << { value: value[1..-1], inclusive: false } else raise Redis::CommandError, "ERR min or max not valid string range item" end end range_options end def get_limit(opts, vals) index = opts.index('LIMIT') if index offset = opts[index + 1] count = opts[index + 2] count = vals.size if count < 0 [offset, count] end end def mapped_param? param param.size == 1 && param[0].is_a?(Array) end # NOTE : Redis-rb 3.x will flatten *args, so method(["a", "b", "c"]) # should be handled the same way as method("a", "b", "c") alias_method :flatten?, :mapped_param? def srandmember_single(key) data_type_check(key, ::Set) return nil unless data[key] data[key].to_a[rand(data[key].size)] end def srandmember_multiple(key, number) return [] unless data[key] if number >= 0 # replace with `data[key].to_a.sample(number)` when 1.8.7 is deprecated (1..number).inject([]) do |selected, _| available_elements = data[key].to_a - selected selected << available_elements[rand(available_elements.size)] end.compact else (1..-number).map { data[key].to_a[rand(data[key].size)] }.flatten end end def bzpop(command, args) timeout = if args.last.is_a?(Hash) args.pop[:timeout] elsif args.last.respond_to?(:to_int) args.pop.to_int end timeout ||= 0 single_pop_command = command.to_s[1..-1] keys = args.flatten keys.each do |key| if data[key] data_type_check(data[key], ZSet) if data[key].size > 0 result = public_send(single_pop_command, key) return result.unshift(key) end end end sleep(timeout.to_f) nil end def sort_keys(arr) # Sort by score, or if scores are equal, key alphanum arr.sort do |(k1, v1), (k2, v2)| if v1 == v2 k1 <=> k2 else v1 <=> v2 end end end end end end # FIXME this line should be deleted as explicit enabling is better Redis::Connection.drivers << Redis::Connection::Memory fakeredis-0.8.0/lib/fake_redis.rb0000644000004100000410000000002413627672425016705 0ustar www-datawww-datarequire "fakeredis" fakeredis-0.8.0/fakeredis.gemspec0000644000004100000410000000164113627672425017026 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "fakeredis/version" Gem::Specification.new do |s| s.name = "fakeredis" s.version = FakeRedis::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Guillermo Iguaran"] s.email = ["guilleiguaran@gmail.com"] s.homepage = "https://guilleiguaran.github.com/fakeredis" s.license = "MIT" s.summary = %q{Fake (In-memory) driver for redis-rb.} s.description = %q{Fake (In-memory) driver for redis-rb. Useful for testing environment and machines without Redis.} s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_runtime_dependency(%q, ["~> 4.1"]) s.add_development_dependency(%q, ["~> 3.0"]) end fakeredis-0.8.0/Gemfile0000644000004100000410000000030013627672425015006 0ustar www-datawww-datasource "https://rubygems.org" gem 'rake' gem 'rdoc' platforms :rbx do gem 'racc' gem 'rubysl', '~> 2.0' gem 'psych' end # Specify your gem's dependencies in fakeredis.gemspec gemspec