├── README.md ├── examples ├── redisScripts │ ├── zunion.lua │ └── zunionextended.lua ├── run.sh ├── zunion_test.lua └── zunionextended_test.lua ├── misc └── removeLuaRocks.sh ├── redis-lua-unit-1.0-0.rockspec ├── redis-lua-unit-1.0-0.src.rock ├── src ├── RedisDb.lua ├── RedisLua.lua ├── commands │ ├── connection.lua │ ├── hash.lua │ ├── keys.lua │ ├── list.lua │ ├── server.lua │ ├── set.lua │ ├── string.lua │ └── zset.lua ├── inspect.lua ├── internals.lua └── redis-mock.lua └── test ├── hash_test.lua ├── keys_test.lua ├── string_test.lua └── zset_test.lua /README.md: -------------------------------------------------------------------------------- 1 | Redis-lua-unit [![Gittip](http://badgr.co/gittip/fgribreau.png)](https://www.gittip.com/fgribreau/) [![Deps](https://david-dm.org/Redsmin/redis-lua-unit.png?a)](https://david-dm.org/Redsmin/redis-lua-unit) 2 | ============== 3 | -------------- 4 | 5 | Redis-lua-unit - Framework agnostic unit-testing for Redis Lua scripts 6 | 7 | **Work in progress, pull requests are welcome!** 8 | 9 | ============== 10 | -------------- 11 | 12 | Installation 13 | ------------ 14 | 15 | wget https://raw.github.com/Redsmin/redis-lua-unit/master/redis-lua-unit-1.0-0.src.rock 16 | luarocks install ./redis-lua-unit-1.0-0.src.rock 17 | 18 | If you don't have the `luarocks` package manager installed run (change the default `2.0.10` version if necessary): 19 | 20 | VERSION="2.0.10";wget "http://luarocks.org/releases/luarocks-${VERSION}.tar.gz" && tar -xvf luarocks-${VERSION}.tar.gz && (cd "luarocks-${VERSION}/";./configure;make;sudo make install);rm -rf "luarocks-${VERSION}/";rm "luarocks-${VERSION}.tar.gz";echo "done"; 21 | 22 | Or on OSX `brew install luarocks` 23 | 24 | Usage 25 | ----- 26 | 27 | `redis-lua-unit` is framework agnostic and may be used with anything. The following examples will be written using [busted](http://olivinelabs.com/busted/). 28 | 29 | ```lua 30 | 31 | -- include redis-mock 32 | runScript, Redis = require("redis-mock")() 33 | -- include your favorite unit-testing framework 34 | 35 | local redis = nil 36 | 37 | describe("Testing redis-lua-unit", function() 38 | 39 | before_each(function() 40 | -- RedisDb Mock instance (the above redis lua script will be able to call redis.call, redis.pcall, ...) 41 | redis = Redis() 42 | end) 43 | 44 | it("should return the cached version when the sha1hex(keys) already exist", function() 45 | -- Keys that will be forwarded to the script 46 | KEYS = {"b:nm:1350247717260", "b:nm:1350247710000"} 47 | 48 | -- Each redis command is available through redis.db:COMMAND 49 | -- if it isn't feel free to submit a pull-request 50 | redis.db:zadd("zunion:sha1hex", 2, "two", 1, "one" , 3, "three") 51 | 52 | spy.on(redis.db, "exists") 53 | 54 | -- runScript require {filename, redis, KEYS} 55 | -- + filename {string} path of the redis lua script 56 | -- + redis {"object"} returned by the globally available Redis() constructor 57 | -- + [KEYS] {table} key names 58 | -- + [ARGV] {table} additional arguments 59 | runScript {filename="redisScripts/zunion.lua", redis=redis, KEYS=KEYS} 60 | 61 | -- Check 62 | assert.spy(redis.db.exists).was.called()) 63 | end) 64 | 65 | end) 66 | ``` 67 | 68 | More examples are available in `examples/`. 69 | 70 | Supported 71 | --------- 72 | 73 | We accept pull-requests ! 74 | 75 | 76 | # lua 77 | [~] call 78 | [~] pcall 79 | [~] sha1hex 80 | 81 | # keys 82 | [x] del 83 | [ ] dump 84 | [x] exists 85 | [~] expire 86 | [ ] expireat 87 | [ ] keys 88 | [ ] migrate 89 | [-] move 90 | [ ] object 91 | [ ] persist 92 | [ ] pexpire 93 | [ ] pexpireat 94 | [ ] pttl 95 | [ ] randomkey 96 | [ ] rename 97 | [ ] renamenx 98 | [ ] restore 99 | [ ] sort 100 | [~] ttl 101 | [x] type 102 | 103 | # strings 104 | [ ] append 105 | [ ] bitcount 106 | [ ] bitop 107 | [ ] decr 108 | [ ] decrby 109 | [x] get 110 | [ ] getbit 111 | [ ] getrange 112 | [ ] getset 113 | [ ] incr 114 | [ ] incrby 115 | [ ] incrbyfloat 116 | [ ] mget 117 | [ ] mset 118 | [ ] msetnx 119 | [ ] psetex 120 | [x] set 121 | [ ] setbit 122 | [ ] setex 123 | [ ] setrange 124 | [x] strlen 125 | 126 | # hashes 127 | [x] hdel 128 | [x] hexists 129 | [x] hget 130 | [ ] hgetall 131 | [ ] hincrby 132 | [ ] hincrbyfloat 133 | [ ] hkeys 134 | [ ] hlen 135 | [ ] hmget 136 | [ ] hmset 137 | [x] hset 138 | [ ] hsetnx 139 | [ ] hvals 140 | 141 | # lists 142 | [ ] blpop 143 | [ ] brpop 144 | [ ] brpoplpus 145 | [ ] lindex 146 | [ ] linsert 147 | [ ] llen 148 | [ ] lpop 149 | [ ] lpush 150 | [ ] lpushx 151 | [ ] lrange 152 | [ ] lrem 153 | [ ] lset 154 | [ ] ltrim 155 | [ ] rpop 156 | [ ] rpoplpush 157 | [ ] rpush 158 | [ ] rpushx 159 | 160 | # sets 161 | [x] sadd 162 | [ ] scard 163 | [ ] sdiff 164 | [ ] sdiffstore 165 | [ ] sinter 166 | [ ] sinterstore 167 | [ ] sismember 168 | [ ] smembers 169 | [ ] smove 170 | [ ] spop 171 | [ ] srandmember 172 | [ ] srem 173 | [ ] sunion 174 | [~] sunionstore 175 | 176 | # sorted sets 177 | [x] zadd 178 | [ ] zcard 179 | [ ] zcount 180 | [ ] zincrby 181 | [ ] zinterstore 182 | [ ] zrange 183 | [ ] zrangebyscore 184 | [ ] zrank 185 | [ ] zrem 186 | [ ] zremrangebyrank 187 | [ ] zremrangebyscore 188 | [~] zrevrange 189 | [ ] zrevrangebyscore 190 | [ ] zrevrank 191 | [ ] zscore 192 | [ ] zunionstore 193 | 194 | # pub/sub 195 | [ ] psubscribe 196 | [ ] publish 197 | [ ] punsubscribe 198 | [ ] subscribe 199 | [ ] unsubscribe 200 | 201 | # transactions 202 | [ ] discard 203 | [ ] exec 204 | [ ] multi 205 | [ ] unwatch 206 | [ ] watch 207 | 208 | # scripting 209 | [ ] eval 210 | [ ] evalsha 211 | [ ] script exists 212 | [ ] script flush 213 | [ ] script kill 214 | [ ] script load 215 | 216 | # connection 217 | [ ] auth 218 | [x] echo 219 | [x] ping 220 | [ ] quit 221 | [-] select 222 | 223 | # server 224 | [ ] bgrewriteaof 225 | [ ] bgsave 226 | [ ] client kill 227 | [ ] client list 228 | [ ] config get 229 | [ ] config set 230 | [ ] config resetstat 231 | [ ] dbsize 232 | [ ] debug object 233 | [ ] debug segfault 234 | [x] flushall 235 | [x] flushdb 236 | [ ] info 237 | [ ] lastsave 238 | [ ] monitor 239 | [ ] save 240 | [ ] shutdown 241 | [ ] slaveof 242 | [ ] slowlog 243 | [ ] sync 244 | [ ] time 245 | 246 | Authors 247 | ------- 248 | 249 | * [Francois-Guillaume Ribreau](http://twitter.com/FGRibreau) 250 | * Original work from [@pchapuis](http://twitter.com/pchapuis) [fakeredis](https://github.com/catwell/cw-lua/tree/master/fakeredis) 251 | 252 | Sponsor 253 | ------- 254 | This project is sponsored by: 255 | * [Redsmin](https://redsmin.com): Full featured GUI that provides online real-time visualization and administration service for Redis. 256 | * [Bringr](http://brin.gr): filter, observe, understand the real-time social web 257 | 258 | Copyright and license 259 | --------------------- 260 | Copyright (c) 2012 Francois-Guillaume Ribreau (npm@fgribreau.com) 261 | 262 | 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: 263 | 264 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 265 | 266 | 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. 267 | -------------------------------------------------------------------------------- /examples/redisScripts/zunion.lua: -------------------------------------------------------------------------------- 1 | -- zunion numkeys key [key ..] 2 | -- simple zunion - MIT LICENSE 3 | -- Francois-Guillaume Ribreau @FGRibreau https://redsmin.com http://brin.gr 4 | 5 | local expire = 5*60; -- expire after 5 minutes 6 | local lgth = #KEYS; 7 | -- Define the key name 8 | local name = "zunion:" .. redis.sha1hex(table.concat(KEYS)) 9 | 10 | local function getResults() 11 | -- return the result from the biggest to the lowest 12 | return redis.call('zrevrange', name, 0, 10, 'WITHSCORES') 13 | end 14 | 15 | -- If the key already exist returns it 16 | if redis.call('exists', name) == 1 then 17 | return getResults() 18 | end 19 | 20 | -- do a zunionstore 21 | table.insert(KEYS, 1, "zunionstore") 22 | table.insert(KEYS, 2, name) 23 | table.insert(KEYS, 3, lgth) 24 | redis.call(unpack(KEYS)); 25 | 26 | -- Add an expire 27 | redis.call("EXPIRE", name, expire); 28 | 29 | -- Return the key 30 | return getResults() -------------------------------------------------------------------------------- /examples/redisScripts/zunionextended.lua: -------------------------------------------------------------------------------- 1 | -- zunion numkeys key [key ..] [REVRANGE start stop] 2 | -- simple zunion - MIT LICENSE 3 | -- Francois-Guillaume Ribreau @FGRibreau https://redsmin.com http://brin.gr 4 | 5 | local expire = 5*60 -- expire after 5 minutes 6 | local lgth = #KEYS 7 | -- Define the key name 8 | local name = "zunion:" .. redis.sha1hex(table.concat(KEYS)) 9 | local start = 0 10 | local stop = -1 11 | 12 | if(#ARGV > 0) then 13 | if(ARGV[1] == "REVRANGE") then 14 | start = tonumber(ARGV[2]) or 0 15 | stop = tonumber(ARGV[3]) or -1 16 | end 17 | end 18 | 19 | local function getResults() 20 | -- return the result from the biggest to the lowest 21 | return redis.call('zrevrange', name, start, stop, 'WITHSCORES') 22 | end 23 | 24 | -- If the key already exist returns it 25 | if redis.call('exists', name) == true then 26 | return getResults() 27 | end 28 | 29 | -- do a zunionstore 30 | table.insert(KEYS, 1, "zunionstore") 31 | table.insert(KEYS, 2, name) 32 | table.insert(KEYS, 3, lgth) 33 | redis.call(unpack(KEYS)); 34 | 35 | -- Add an expire 36 | redis.call("expire", name, expire); 37 | 38 | -- Return the key 39 | return getResults() -------------------------------------------------------------------------------- /examples/run.sh: -------------------------------------------------------------------------------- 1 | clear && busted zunion_test.lua && (cd ../test/;busted string_test.lua && busted keys_test.lua) 2 | -------------------------------------------------------------------------------- /examples/zunion_test.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path .. ";../?.lua;../src/?.lua" 2 | local runScript, Redis = require("redis-mock")() 3 | require "busted" 4 | 5 | local r = nil 6 | describe("Zunion without range args", function() 7 | 8 | before_each(function() 9 | -- RedisDb Mock instance 10 | r = Redis() 11 | end) 12 | 13 | it("should return the cached version when the sha1hex(keys) already exist", function() 14 | -- Keys 15 | KEYS = {"b:nm:1350247717260", "b:nm:1350247710000"} 16 | 17 | -- Setup 18 | r.db:zadd("zunion:sha1hex", 2, "two", 1, "one" , 3, "three") 19 | spy.on(r.db, "exists") 20 | 21 | -- Run 22 | runScript {filename="redisScripts/zunion.lua", redis=r, KEYS=KEYS} 23 | 24 | -- Test 25 | assert.same(assert.spy(r.db.exists).payload.calls[3], "zunion:sha1hex") 26 | end) 27 | 28 | it("should compute the zunion otherwise", function() 29 | -- Keys 30 | KEYS = {"b:nm:1350247717260", "b:nm:1350248810000"} 31 | 32 | -- Setup 33 | r.db:zadd("b:nm:1350247717260", 10, "marc", 1, "paul", 8, "max", 3, "marie", 14, "jean") 34 | r.db:zadd("b:nm:1350248810000", 10, "silvia", 15, "manon", 9, "maxwell", 1, "marc") 35 | 36 | -- Run 37 | local ret = runScript {filename="redisScripts/zunion.lua", redis=r, KEYS=KEYS} 38 | 39 | -- Test 40 | assert.same(ret, { { "manon", 15 }, { "jean", 14 }, { "marc", 11 }, { "silvia", 10 }, { "maxwell", 9 }, { "max", 8 }, { "marie", 3 }, { "paul", 1 } }) 41 | end) 42 | 43 | end) -------------------------------------------------------------------------------- /examples/zunionextended_test.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path .. ";../?.lua;../src/?.lua" 2 | local runScript, Redis = require("redis-mock")() 3 | require "busted" 4 | 5 | local r = nil 6 | describe("Zunion v2 with range args", function() 7 | 8 | before_each(function() 9 | -- RedisDb Mock instance 10 | r = Redis() 11 | end) 12 | 13 | it("should return REVRANGE 0 4", function() 14 | -- Setup 15 | r.db:zadd("myzset1", 10, "marc", 1, "paul", 8, "max", 3, "marie", 14, "jean") 16 | r.db:zadd("myzset2", 10, "silvia", 15, "manon", 9, "maxwell", 1, "marc", 0, "lucie") 17 | 18 | KEYS = {"myzset1", "myzset2"} 19 | 20 | -- Run 21 | local ret = runScript {filename="redisScripts/zunionextended.lua", redis=r, KEYS=KEYS, ARGV={"REVRANGE", 0, 4}} 22 | 23 | -- Test 24 | assert.same(ret, {{ "manon", 15 },{ "jean", 14 },{ "marc", 11 },{ "silvia", 10 },{ "maxwell", 9 } }) 25 | end) 26 | 27 | it("should return REVRANGE 0 -1", function() 28 | -- Setup 29 | r.db:zadd("myzset1", 30, "marc", 1, "paul", 8, "max", 3, "marie", 14, "jean") 30 | r.db:zadd("myzset2", 10, "silvia", 15, "manon", 9, "maxwell", 1, "marc") 31 | 32 | KEYS = {"myzset1", "myzset2"} 33 | spy.on(r.db, "expire") 34 | 35 | -- Run 36 | local ret = runScript {filename="redisScripts/zunionextended.lua", redis=r, KEYS=KEYS, ARGV={"REVRANGE", 0, -1}} 37 | 38 | 39 | -- Test 40 | assert.same(r.db.expire.calls[1][2], "zunion:sha1hex") 41 | assert.same(r.db.expire.calls[1][3], 300) 42 | 43 | assert.same(ret, { { "marc", 31 }, { "manon", 15 }, { "jean", 14 }, { "silvia", 10 }, { "maxwell", 9 }, { "max", 8 }, { "marie", 3 }, { "paul", 1 } }) 44 | end) 45 | 46 | it("should return the cached version", function() 47 | -- Setup 48 | r.db:zadd("zunion:sha1hex", 40, "marc", 1, "paul", 8, "max", 3, "marie", 14, "jean") 49 | 50 | r.db:zadd("myzset1", 30, "lmarc", 1, "lpaul", 8, "lmax", 3, "lmarie", 14, "ljean") 51 | r.db:zadd("myzset2", 10, "lsilvia", 15, "lmanon", 9, "lmaxwell", 1, "lmarc") 52 | 53 | spy.on(r.db, "zunionstore") 54 | 55 | 56 | KEYS = {"myzset1", "myzset2"} 57 | 58 | local s = spy.on(r.db, "expire") 59 | 60 | -- Run 61 | local ret = runScript {filename="redisScripts/zunionextended.lua", redis=r, KEYS=KEYS, ARGV={"REVRANGE", 0, -1}} 62 | 63 | -- Test 64 | 65 | -- expire should not be called 66 | assert.same(#r.db.expire.calls, 0) 67 | -- should return zunion:sha1hex 68 | assert.same(ret, {{ "marc", 40 },{ "jean", 14 },{ "max", 8 },{ "marie", 3 },{ "paul", 1 } }) 69 | end) 70 | 71 | end) 72 | -------------------------------------------------------------------------------- /misc/removeLuaRocks.sh: -------------------------------------------------------------------------------- 1 | sudo rm -rf /usr/local/luarocks* 2 | sudo rm -rf /usr/local/share/lua/5.1/luarocks* 3 | sudo rm -rf /usr/local/lib/luarocks* 4 | sudo rm -rf /usr/local/share/lua/5.2/luarocks 5 | sudo rm -f /usr/local/bin/luarocks 6 | -------------------------------------------------------------------------------- /redis-lua-unit-1.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "redis-lua-unit" 2 | version = "1.0-0" 3 | source = { 4 | url = "https://github.com/Redsmin/redis-lua-unit/tarball/master", 5 | dir = "redis-lua-unit" 6 | } 7 | description = { 8 | summary = "Give some unit-test love to your Redis LUA scripts", 9 | detailed = [[ 10 | 11 | ]], 12 | homepage = "https://github.com/Redsmin/redis-lua-unit", 13 | license = "MIT " 14 | } 15 | dependencies = { 16 | "lua >= 5.1", 17 | "busted >= 1.3-1", 18 | "luafilesystem >= 1.6.2-1" 19 | } 20 | build = { 21 | type = "builtin", 22 | modules = { 23 | -- ["busted.core"] = "src/core.lua", 24 | -- ["busted.output.utf_terminal"] = "src/output/utf_terminal.lua", 25 | -- ["busted.output.plain_terminal"] = "src/output/plain_terminal.lua", 26 | -- ["busted.output.TAP"] = "src/output/TAP.lua", 27 | -- ["busted.output.json"] = "src/output/json.lua", 28 | -- ["busted.init"] = "src/init.lua", 29 | -- ["busted.languages.en"] = "src/languages/en.lua", 30 | -- ["busted.languages.ar"] = "src/languages/ar.lua", 31 | -- ["busted.languages.fr"] = "src/languages/fr.lua", 32 | -- ["busted.languages.nl"] = "src/languages/nl.lua", 33 | -- ["busted.languages.ru"] = "src/languages/ru.lua", 34 | -- ["busted.languages.ua"] = "src/languages/ua.lua", 35 | }, 36 | install = { 37 | -- bin = { 38 | -- ["busted"] = "bin/busted", 39 | -- ["busted.bat"] = "bin/busted.bat", 40 | -- ["busted_bootstrap"] = "bin/busted_bootstrap" 41 | -- } 42 | } 43 | } -------------------------------------------------------------------------------- /redis-lua-unit-1.0-0.src.rock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redsmin/redis-lua-unit/272da00b83e5470976046f45d85d2509c7ea891b/redis-lua-unit-1.0-0.src.rock -------------------------------------------------------------------------------- /src/RedisDb.lua: -------------------------------------------------------------------------------- 1 | -- RedisDb.lua 2 | -- Mock the "redis" database commands 3 | -- Original version from https://github.com/catwell/cw-lua/tree/master/fakeredis by Pierre Chapuis 4 | 5 | -- RedisDb mock 6 | local db = {} 7 | 8 | local methods = { 9 | RedisDb_VERBOSE = false 10 | } 11 | 12 | --- internal 13 | require("internals")(methods) 14 | 15 | -- keys 16 | require("commands/keys")(methods) 17 | 18 | -- strings 19 | require("commands/string")(methods) 20 | 21 | -- hashes 22 | require("commands/hash")(methods) 23 | 24 | -- sorted set 25 | require("commands/zset")(methods) 26 | 27 | -- connection 28 | require("commands/connection")(methods) 29 | 30 | -- server 31 | require("commands/server")(methods) 32 | 33 | -- Constructor 34 | local function RedisDb() 35 | local obj = {} 36 | return setmetatable(obj,{__index = methods}) 37 | end 38 | 39 | return RedisDb -------------------------------------------------------------------------------- /src/RedisLua.lua: -------------------------------------------------------------------------------- 1 | -- RedisLua.lua 2 | -- Mock the "redis" object available from the LUA Script 3 | 4 | -- Methods container 5 | local _Redis = {} 6 | 7 | -- Redis LUA methods 8 | local call = function(self) 9 | return (function(cmd, ...) 10 | cmd = string.lower(cmd) 11 | 12 | local arg = {...} 13 | 14 | if(not self.db[cmd]) then 15 | print("[", cmd, "] Unimplemented, we accept pull-requests http://bit.ly/QGf8wB !") 16 | print("[", cmd, "] Unimplemented, returning true") 17 | return true 18 | end 19 | 20 | local ret = self.db[cmd](self.db, unpack(arg)) 21 | 22 | if self.RedisLua_VERBOSE then 23 | print(cmd .. "( " .. table.concat(arg, " ") .. " ) === ".. tostring(ret)) 24 | end 25 | 26 | return ret 27 | end) 28 | end 29 | 30 | local pcall = function(self) 31 | -- @todo better version 32 | return call(self) 33 | end 34 | 35 | 36 | local sha1hex = function(self) 37 | return (function(arg) 38 | -- @todo implement sha1hex 39 | return (args or "sha1hex") 40 | end) 41 | end 42 | 43 | local RedisDb = require("/RedisDb") 44 | 45 | -- Constructor 46 | local function RedisLua() 47 | local obj = {} 48 | obj.db = RedisDb() 49 | obj.RedisLua_VERBOSE = false 50 | obj.call = call(obj) 51 | obj.pcall = pcall(obj) 52 | obj.sha1hex = sha1hex(obj) 53 | return obj 54 | end 55 | 56 | return RedisLua -------------------------------------------------------------------------------- /src/commands/connection.lua: -------------------------------------------------------------------------------- 1 | return (function(RedisDb) 2 | function RedisDb:echo(self,v) 3 | assert(type(v) == "string") 4 | return v 5 | end 6 | 7 | function RedisDb:ping(self) 8 | return "PONG" 9 | end 10 | end) -------------------------------------------------------------------------------- /src/commands/hash.lua: -------------------------------------------------------------------------------- 1 | return (function(RedisDb) 2 | 3 | function RedisDb:hdel(self,k, ...) 4 | local arg = {...} 5 | assert(#arg > 0) 6 | local r = 0 7 | local x = RedisDb.xgetw(self,k,"hash") 8 | for i=1,#arg do 9 | assert((type(arg[i]) == "string")) 10 | if x[arg[i]] then r = r + 1 end 11 | x[arg[i]] = nil 12 | end 13 | if empty(self,k) then self[k] = nil end 14 | return r 15 | end 16 | 17 | function RedisDb:hexists(self,k,k2) 18 | return not not RedisDb.hget(self,k,k2) 19 | end 20 | 21 | function RedisDb:hget(self,k,k2) 22 | assert((type(k2) == "string")) 23 | local x = RedisDb.xgetr(self,k,"hash") 24 | return x[k2] 25 | end 26 | 27 | function RedisDb:hset(self,k,k2,v) 28 | assert((type(k2) == "string") and (type(v) == "string")) 29 | local x = RedisDb.xgetw(self,k,"hash") 30 | x[k2] = v 31 | return true 32 | end 33 | 34 | end) -------------------------------------------------------------------------------- /src/commands/keys.lua: -------------------------------------------------------------------------------- 1 | return (function(RedisDb) 2 | 3 | function RedisDb:del(...) 4 | local arg = {...} 5 | assert(#arg > 0) 6 | local r = 0 7 | for i=1,#arg do 8 | if self[arg[i]] then r = r + 1 end 9 | self[arg[i]] = nil 10 | end 11 | return r 12 | end 13 | 14 | function RedisDb:exists(key) 15 | local returnValue = not not self[key] 16 | 17 | return RedisDb.printCmd(self, 'exists', key, returnValue) 18 | end 19 | 20 | function RedisDb:_type(k) 21 | return (self[k] and self[k].ktype) and self[k].ktype or "none" 22 | end 23 | 24 | function RedisDb:expire(key, seconds) 25 | local ret = false 26 | 27 | if(RedisDb.exists(self, key)) then 28 | self[key].expire = seconds 29 | ret = true 30 | end 31 | 32 | return RedisDb.printCmd(self, 'expire', key, seconds, ret) 33 | end 34 | 35 | -- Integer reply: TTL in seconds or -1 when key does not exist or does not have a timeout. 36 | function RedisDb:ttl( key) 37 | if(not RedisDb.exists(self, key) or not self[key].expire) then return RedisDb.printCmd(self, 'ttl', key, false) end 38 | 39 | local returnValue = self[key].expire 40 | 41 | return RedisDb.printCmd(self, 'ttl', key, returnValue) 42 | end 43 | 44 | end) -------------------------------------------------------------------------------- /src/commands/list.lua: -------------------------------------------------------------------------------- 1 | return (function(RedisDb) 2 | 3 | end) -------------------------------------------------------------------------------- /src/commands/server.lua: -------------------------------------------------------------------------------- 1 | return (function(RedisDb) 2 | 3 | function RedisDb:flushdb() 4 | for k,_ in pairs(self) do self[k] = nil end 5 | return true 6 | end 7 | 8 | end) -------------------------------------------------------------------------------- /src/commands/set.lua: -------------------------------------------------------------------------------- 1 | return (function(methods) 2 | 3 | end) -------------------------------------------------------------------------------- /src/commands/string.lua: -------------------------------------------------------------------------------- 1 | return (function(RedisDb) 2 | 3 | function RedisDb:get(key) 4 | local x = RedisDb.xgetr(self, key, "string") 5 | local returnValue = x[1] 6 | return RedisDb.printCmd(self, 'get', key, returnValue or false) 7 | end 8 | 9 | function RedisDb:set( key, v) 10 | assert(type(v) == "string") 11 | self[key] = {ktype="string",value={v}} 12 | return RedisDb.printCmd(self, 'set', key, v, true) 13 | end 14 | 15 | function RedisDb:strlen(k) 16 | local x = RedisDb.xgetr(self,k,"string") 17 | return x[1] and #x[1] or 0 18 | end 19 | 20 | end) -------------------------------------------------------------------------------- /src/commands/zset.lua: -------------------------------------------------------------------------------- 1 | -- Implementation: self[key].value = {{"plop",10}, {"hey",12}, {"hello",1}} 2 | 3 | return (function(RedisDb) 4 | local MEMBER = 1 5 | local SCORE = 2 6 | 7 | -- local zadd = function(self, key, ...) 8 | function RedisDb:zadd(key, ...) 9 | local args = {...} 10 | assert(#args > 0, "zadd require at least on pair of score/member") 11 | 12 | -- create the sorted set if it doesn't not exist 13 | if(not RedisDb.exists(self, key)) then 14 | self[key] = {ktype="zset",value={}, _members={}} 15 | end 16 | 17 | local zset = self[key].value 18 | local oldSize = #self[key].value 19 | 20 | for i=1,#args, 2 do 21 | local score = tonumber(args[i]) 22 | local member = tostring(args[i+1]) 23 | 24 | assert(type(score) == "number", "Score must be a number") 25 | assert(type(member) == "string", "Member must be a number") 26 | 27 | if(not zset[member]) then 28 | table.insert(self[key]._members, member) 29 | table.insert(zset, {member, score}) 30 | else 31 | -- linear lookup 32 | 33 | for curMember,oldScore in pairs(zset) do 34 | if(zset[curMember][1] == member) then 35 | zset[member].score = oldScore + score 36 | break 37 | end 38 | end 39 | end 40 | 41 | end 42 | 43 | -- sort the sorted-set 44 | table.sort(zset, function(a,b) return a[SCORE] > b[SCORE] end) 45 | 46 | 47 | return RedisDb.printCmd(self, 'zadd', key, unpack(args), #self[key].value - oldSize) 48 | end 49 | 50 | function RedisDb:zrevrange(key, _start, _stop, withscores) 51 | local ret = {} 52 | 53 | start = tonumber(_start) 54 | stop = tonumber(_stop) 55 | 56 | assert(type(key) == "string", "key is required") 57 | assert(type(start) == "number", "start is required") 58 | assert(type(stop) == "number", "stop is required") 59 | 60 | if(not withscores or not type(withscores) == "string") then 61 | withscores = nil 62 | end 63 | 64 | if(RedisDb.exists(self, key)) then 65 | local zset = RedisDb.xgetr(self, key, "zset") 66 | start = start + 1 67 | if(stop == -1 or stop+1 > #zset)then stop = #zset else stop = stop + 1 end 68 | 69 | -- @todo - optimize that 70 | for i=start,stop do 71 | if(not withscores) then 72 | table.insert(ret, zset[i][1]) -- only insert the member 73 | else 74 | table.insert(ret, zset[i]) 75 | end 76 | end 77 | else 78 | end 79 | 80 | return RedisDb.printCmd(self, 'zrevrange', key, _start, _stop, withscores or "", ret) 81 | end 82 | 83 | -- ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] 84 | -- Return: the number of elements in the resulting sorted set at destination. 85 | -- @todo support [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] 86 | function RedisDb:zunionstore(destination, numkeys, ...) 87 | local keys = {...} 88 | local hash = {} 89 | 90 | for i=1, numkeys do 91 | assert(keys[i], "numkeys must specify the right number of specified keys") 92 | 93 | local zset = RedisDb.zrevrange(self, keys[i], 0, -1, "WITHSCORES") 94 | 95 | if(zset) then 96 | 97 | -- loop over the members and add them to the hash 98 | for i=1,#zset do 99 | 100 | local member = zset[i][MEMBER] 101 | local score = zset[i][SCORE] 102 | 103 | if(not hash[member]) then 104 | hash[member] = score 105 | else 106 | hash[member] = hash[member] + score 107 | end 108 | 109 | end -- end loop 110 | 111 | end -- end if zset 112 | 113 | end 114 | 115 | -- add into the destination zset 116 | -- @todo do only one zadd() call 117 | for member,score in pairs(hash) do 118 | RedisDb.zadd(self, destination, score, member) 119 | end 120 | 121 | return RedisDb.printCmd(self, 'zunionstore', destination, numkeys, unpack(keys), true) 122 | end 123 | 124 | 125 | end) -------------------------------------------------------------------------------- /src/inspect.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------------------------------------------------- 2 | -- inspect.lua - v1.1.1 (2011-01) 3 | -- Enrique García Cota - enrique.garcia.cota [AT] gmail [DOT] com 4 | -- human-readable representations of tables. 5 | -- inspired by http://lua-users.org/wiki/TableSerialization 6 | ----------------------------------------------------------------------------------------------------------------------- 7 | 8 | -- Apostrophizes the string if it has quotes, but not aphostrophes 9 | -- Otherwise, it returns a regular quoted string 10 | local function smartQuote(str) 11 | if string.match( string.gsub(str,"[^'\"]",""), '^"+$' ) then 12 | return "'" .. str .. "'" 13 | end 14 | return string.format("%q", str ) 15 | end 16 | 17 | local controlCharsTranslation = { 18 | ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", 19 | ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\\"] = "\\\\" 20 | } 21 | 22 | local function unescapeChar(c) return controlCharsTranslation[c] end 23 | 24 | local function unescape(str) 25 | local result, _ = string.gsub( str, "(%c)", unescapeChar ) 26 | return result 27 | end 28 | 29 | local function isIdentifier(str) 30 | return string.match( str, "^[_%a][_%a%d]*$" ) 31 | end 32 | 33 | local function isArrayKey(k, length) 34 | return type(k)=='number' and 1 <= k and k <= length 35 | end 36 | 37 | local function isDictionaryKey(k, length) 38 | return not isArrayKey(k, length) 39 | end 40 | 41 | local sortOrdersByType = { 42 | ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, 43 | ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 44 | } 45 | 46 | local function sortKeys(a,b) 47 | local ta, tb = type(a), type(b) 48 | if ta ~= tb then return sortOrdersByType[ta] < sortOrdersByType[tb] end 49 | if ta == 'string' or ta == 'number' then return a < b end 50 | return false 51 | end 52 | 53 | local function getDictionaryKeys(t) 54 | local length = #t 55 | local keys = {} 56 | for k,_ in pairs(t) do 57 | if isDictionaryKey(k, length) then table.insert(keys,k) end 58 | end 59 | table.sort(keys, sortKeys) 60 | return keys 61 | end 62 | 63 | local function getToStringResultSafely(t, mt) 64 | local __tostring = type(mt) == 'table' and mt.__tostring 65 | local string, status 66 | if type(__tostring) == 'function' then 67 | status, string = pcall(__tostring, t) 68 | string = status and string or 'error: ' .. tostring(string) 69 | end 70 | return string 71 | end 72 | 73 | local Inspector = {} 74 | 75 | function Inspector:new(v, depth) 76 | local inspector = { 77 | buffer = {}, 78 | depth = depth, 79 | level = 0, 80 | counters = { 81 | ['function'] = 0, 82 | ['userdata'] = 0, 83 | ['thread'] = 0, 84 | ['table'] = 0 85 | }, 86 | pools = { 87 | ['function'] = setmetatable({}, {__mode = "kv"}), 88 | ['userdata'] = setmetatable({}, {__mode = "kv"}), 89 | ['thread'] = setmetatable({}, {__mode = "kv"}), 90 | ['table'] = setmetatable({}, {__mode = "kv"}) 91 | } 92 | } 93 | 94 | setmetatable( inspector, { 95 | __index = Inspector, 96 | __tostring = function(instance) return table.concat(instance.buffer) end 97 | } ) 98 | return inspector:putValue(v) 99 | end 100 | 101 | function Inspector:puts(...) 102 | local args = {...} 103 | for i=1, #args do 104 | table.insert(self.buffer, tostring(args[i])) 105 | end 106 | return self 107 | end 108 | 109 | function Inspector:tabify() 110 | self:puts("\n", string.rep(" ", self.level)) 111 | return self 112 | end 113 | 114 | function Inspector:up() 115 | self.level = self.level - 1 116 | end 117 | 118 | function Inspector:down() 119 | self.level = self.level + 1 120 | end 121 | 122 | function Inspector:putComma(comma) 123 | if comma then self:puts(',') end 124 | return true 125 | end 126 | 127 | function Inspector:putTable(t) 128 | if self:alreadySeen(t) then 129 | self:puts('') 130 | elseif self.level >= self.depth then 131 | self:puts('{...}') 132 | else 133 | self:puts('<',self:getOrCreateCounter(t),'>{') 134 | self:down() 135 | 136 | local length = #t 137 | local mt = getmetatable(t) 138 | 139 | local string = getToStringResultSafely(t, mt) 140 | if type(string) == 'string' and #string > 0 then 141 | self:puts(' -- ', unescape(string)) 142 | if length >= 1 then self:tabify() end -- tabify the array values 143 | end 144 | 145 | local comma = false 146 | for i=1, length do 147 | comma = self:putComma(comma) 148 | self:puts(' '):putValue(t[i]) 149 | end 150 | 151 | local dictKeys = getDictionaryKeys(t) 152 | 153 | for _,k in ipairs(dictKeys) do 154 | comma = self:putComma(comma) 155 | self:tabify():putKey(k):puts(' = '):putValue(t[k]) 156 | end 157 | 158 | if mt then 159 | comma = self:putComma(comma) 160 | self:tabify():puts(' = '):putValue(mt) 161 | end 162 | self:up() 163 | 164 | if #dictKeys > 0 or mt then -- dictionary table. Justify closing } 165 | self:tabify() 166 | elseif length > 0 then -- array tables have one extra space before closing } 167 | self:puts(' ') 168 | end 169 | self:puts('}') 170 | end 171 | return self 172 | end 173 | 174 | function Inspector:alreadySeen(v) 175 | local tv = type(v) 176 | return self.pools[tv][v] ~= nil 177 | end 178 | 179 | function Inspector:getOrCreateCounter(v) 180 | local tv = type(v) 181 | local current = self.pools[tv][v] 182 | if not current then 183 | current = self.counters[tv] + 1 184 | self.counters[tv] = current 185 | self.pools[tv][v] = current 186 | end 187 | return current 188 | end 189 | 190 | function Inspector:putValue(v) 191 | local tv = type(v) 192 | 193 | if tv == 'string' then 194 | self:puts(smartQuote(unescape(v))) 195 | elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then 196 | self:puts(tostring(v)) 197 | elseif tv == 'table' then 198 | self:putTable(v) 199 | else 200 | self:puts('<',tv,' ',self:getOrCreateCounter(v),'>') 201 | end 202 | return self 203 | end 204 | 205 | function Inspector:putKey(k) 206 | if type(k) == "string" and isIdentifier(k) then 207 | return self:puts(k) 208 | end 209 | return self:puts( "[" ):putValue(k):puts("]") 210 | end 211 | 212 | local function inspect(t, depth) 213 | depth = depth or 4 214 | return tostring(Inspector:new(t, depth)) 215 | end 216 | 217 | return inspect 218 | 219 | -------------------------------------------------------------------------------- /src/internals.lua: -------------------------------------------------------------------------------- 1 | local inspect = require('inspect') 2 | return (function(RedisDb) 3 | 4 | RedisDb.inspect = inspect 5 | 6 | -- http://lua-users.org/wiki/TableSerialization 7 | function RedisDb:table_print(tt, indent, done) 8 | return inspect(tt) 9 | end 10 | 11 | 12 | function RedisDb:xgetr(k,ktype) 13 | if self[k] then 14 | assert(self[k].ktype == ktype, "xgetr ktype(".. k .."): ".. self[k].ktype.." === ".. ktype) 15 | assert(self[k].value) 16 | return self[k].value 17 | else return {} end 18 | end 19 | 20 | function RedisDb:xgetw(k,ktype) 21 | if self[k] and self[k].value then 22 | assert(self[k].ktype == ktype) 23 | else 24 | self[k] = {ktype=ktype,value={}} 25 | end 26 | return self[k].value 27 | end 28 | 29 | function RedisDb:empty(k) 30 | return #self[k].value == 0 31 | end 32 | 33 | -- first parameter is the command name, then option arguments 34 | -- last parameter is the returned value 35 | -- printCmd( cmd, [optional arguments], returnValue) 36 | function RedisDb:printCmd( cmd, ...) 37 | local args = {...} 38 | 39 | if(#args == 0) then 40 | args = {} 41 | end 42 | 43 | local returnValue = args[#args] 44 | local printedValue = returnValue 45 | 46 | if(type(printedValue) == "table") then 47 | printedValue = RedisDb.table_print(printedValue) 48 | else 49 | printedValue = tostring(printedValue) 50 | end 51 | 52 | 53 | if RedisDb.RedisDb_VERBOSE then 54 | print(cmd .. "(" .. inspect(args) .. ") === " .. printedValue) 55 | end 56 | 57 | return returnValue 58 | end 59 | end) -------------------------------------------------------------------------------- /src/redis-mock.lua: -------------------------------------------------------------------------------- 1 | local lfs = require "lfs" 2 | local Redis = require("/RedisLua") 3 | 4 | package.path = package.path .. ";../?.lua;../src/?.lua" 5 | 6 | -- Runscript 7 | local runScript = function(args) 8 | assert(type(args) == "table", "Call runScript with a table like this: runScript {filename=\"/my/script.lua\", redis=redis, KEYS=KEYS}") 9 | assert(type(args.redis) == "table", "A instance of Redis() is required") 10 | 11 | -- quick&dirty find a way to provide keys & redis inside their own context 12 | redis = args.redis 13 | KEYS = args.KEYS or {} 14 | ARGV = args.ARGV or {} 15 | local f = assert(loadfile(lfs.currentdir() .. "/" .. args.filename), "Couldn't load ".. lfs.currentdir() .. "/" .. args.filename) 16 | return f() 17 | end 18 | 19 | return (function() 20 | return runScript, Redis 21 | end) -------------------------------------------------------------------------------- /test/hash_test.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redsmin/redis-lua-unit/272da00b83e5470976046f45d85d2509c7ea891b/test/hash_test.lua -------------------------------------------------------------------------------- /test/keys_test.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path .. ";../?.lua;../src/?.lua" 2 | 3 | local runScript, Redis = require("redis-mock")() 4 | require "busted" 5 | 6 | -- Verbose mode 7 | -- RedisLua_VERBOSE = true 8 | -- RedisDb_VERBOSE = true 9 | 10 | local r = nil 11 | describe("RedisDb Keys", function() 12 | 13 | describe("exists", function() 14 | before_each(function() 15 | -- RedisDb Mock instance 16 | r = Redis() 17 | end) 18 | it("should return true when a key exist", function() 19 | -- Check 20 | assert.are.same(r.db:exists("myKey"), false) 21 | 22 | -- Setup 23 | r.db:set("myKey", "ok") 24 | 25 | -- Test 26 | assert.are.same(r.db:exists("myKey"), true) 27 | end) 28 | end) 29 | 30 | describe("expire", function() 31 | before_each(function() 32 | -- RedisDb Mock instance 33 | r = Redis() 34 | end) 35 | 36 | it("should return false if the key does not exist", function() 37 | assert.are.same(r.db:exists("myKey"), false) 38 | assert.are.same(r.db:expire("myKey", 10), false) 39 | end) 40 | 41 | it("should return false if the key does not exist", function() 42 | -- Check 43 | assert.are.same(r.db:exists("myKey"), false) 44 | 45 | -- Setup 46 | r.db:set("myKey", "ok") 47 | 48 | -- Test 49 | assert.are.same(r.db:expire("myKey", 10), true) 50 | end) 51 | end) 52 | 53 | describe("ttl", function() 54 | before_each(function() 55 | -- RedisDb Mock instance 56 | r = Redis() 57 | end) 58 | 59 | it("should return false if the key does not exist", function() 60 | assert.are.same(r.db:ttl("myKey"), false) 61 | end) 62 | 63 | it("should return false if the key does not have a timeout", function() 64 | -- Check 65 | assert.are.same(r.db:exists("myKey"), false) 66 | 67 | -- Setup 68 | r.db:set("myKey", "ok") 69 | 70 | -- Test 71 | assert.are.same(r.db:ttl("myKey"), false) 72 | end) 73 | 74 | it("should return TTL in seconds if the key have a timeout", function() 75 | -- Setup 76 | r.db:set("myKey", "ok") 77 | r.db:expire("myKey", 60) -- 1 min. 78 | 79 | assert.are.same(r.db:ttl("myKey"), 60) 80 | end) 81 | end) 82 | 83 | after_each(function() 84 | r = nil 85 | end) 86 | end) -------------------------------------------------------------------------------- /test/string_test.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path .. ";../?.lua;../src/?.lua" 2 | 3 | local runScript, Redis = require("redis-mock")() 4 | require "busted" 5 | 6 | -- Verbose mode 7 | -- RedisLua_VERBOSE = true 8 | 9 | local r = nil 10 | describe("RedisDb String", function() 11 | before_each(function() 12 | -- RedisDb Mock instance 13 | r = Redis() 14 | end) 15 | 16 | it("should support set/get", function() 17 | assert.are.same(r.db:get("myKey"), false) 18 | -- Setup 19 | r.db:set("myKey", "ok") 20 | 21 | -- Test 22 | assert.are.same(r.db:get("myKey"), "ok") 23 | end) 24 | 25 | -- it("should support del", function() 26 | -- assert.are.same(r.db:get("myKey"), false) 27 | -- -- Setup 28 | -- r.db:set("myKey", "ok") 29 | -- r.db:del("myKey") 30 | 31 | -- -- Test 32 | -- assert.are.same(r.db:exist("myKey"), false) 33 | -- end) 34 | end) -------------------------------------------------------------------------------- /test/zset_test.lua: -------------------------------------------------------------------------------- 1 | -- support relative path 2 | package.path = package.path .. ";../?.lua;../src/?.lua" 3 | 4 | local runScript, Redis = require("redis-mock")() 5 | require "busted" 6 | 7 | -- Verbose mode 8 | -- RedisLua_VERBOSE = true 9 | -- RedisDb_VERBOSE = true 10 | 11 | local r = nil 12 | 13 | describe("RedisDb Sorted set", function() 14 | describe("zadd", function() 15 | before_each(function() 16 | -- RedisDb Mock instance 17 | r = Redis() 18 | end) 19 | 20 | it("should return the number elements added to the sorted sets, not including elements already existing for which the score was updated.", function() 21 | -- Check 22 | assert.are.same(r.db:exists("myzset"), false) 23 | 24 | -- Setup 25 | local r1 = r.db:zadd("myzset", 10, "marc", 1, "paul", 9, "max", 3, "marie", 14, "jean") 26 | local ret = r.db:zrevrange("myzset", 0, -1, "WITHSCORES") 27 | 28 | -- Test 29 | assert.are.same(r1, 5) 30 | assert.are.same(ret, {{"jean" ,14},{"marc" ,10},{"max" ,9}, {"marie", 3},{"paul", 1}}) 31 | end) 32 | end) 33 | 34 | describe("zrevrange", function() 35 | before_each(function() 36 | -- RedisDb Mock instance 37 | r = Redis() 38 | end) 39 | 40 | it("should return all elements (value) in the sorted set stored at key", function() 41 | -- Setup 42 | local r1 = r.db:zadd("myzset", 2, "two", 1, "one" , 10, "three") 43 | 44 | -- Test 45 | local ret = r.db:zrevrange("myzset", 0, -1) 46 | 47 | -- Check 48 | assert.are.same(ret, {"three", "two", "one"}) 49 | end) 50 | 51 | it("should return the specified range of elements (value) in the sorted set stored at key", function() 52 | -- Setup 53 | local r1 = r.db:zadd("myzset", 10, "marc", 1, "paul", 8, "max", 3, "marie", 14, "jean", 45, "lucie") 54 | 55 | -- Test 56 | local ret = r.db:zrevrange("myzset", 0, 4) 57 | 58 | -- Check 59 | assert.are.same(ret, { "lucie", "jean", "marc", "max", "marie" }) 60 | end) 61 | 62 | it("should return the specified range of elements (key, value) in the sorted set stored at key", function() 63 | -- Setup 64 | local r1 = r.db:zadd("myzset", 2, "two", 1, "one" , 3, "three") 65 | 66 | -- Test 67 | local ret = r.db:zrevrange("myzset", 0, -1, 'WITHSCORES') 68 | 69 | -- Check 70 | assert.are.same(ret, {{"three", 3}, {"two", 2}, {"one", 1}}) 71 | end) 72 | end) 73 | 74 | describe("zunionstore", function() 75 | before_each(function() 76 | -- RedisDb Mock instance 77 | r = Redis() 78 | end) 79 | 80 | it("should return the specified range of elements (value) in the sorted set stored at key", function() 81 | -- Setup 82 | r.db:zadd("myzset1", 10, "marc", 1, "paul", 8, "max", 3, "marie", 14, "jean") 83 | r.db:zadd("myzset2", 10, "silvia", 15, "manon", 9, "maxwell", 1, "marc") 84 | 85 | -- Test 86 | local ret = r.db:zunionstore("out", 2, "myzset1", "myzset2") 87 | local zset = r.db:zrevrange("out", 0, -1, 'WITHSCORES') 88 | 89 | -- Check 90 | assert.same(zset, { { "manon", 15 }, { "jean", 14 }, { "marc", 11 }, { "silvia", 10 }, { "maxwell", 9 }, { "max", 8 }, { "marie", 3 }, { "paul", 1 } }) 91 | end) 92 | end) 93 | end) --------------------------------------------------------------------------------