diff --git a/Database/Redis/ByteStringClass.hs b/Database/Redis/ByteStringClass.hs
--- a/Database/Redis/ByteStringClass.hs
+++ b/Database/Redis/ByteStringClass.hs
@@ -19,6 +19,10 @@
     toBS   = concat . L.toChunks
     fromBS = L.fromChunks . return
 
+instance BS Char where
+    toBS c = U.fromString [c]
+    fromBS = Prelude.head . U.toString
+
 instance BS String where
     toBS   = U.fromString
     fromBS = U.toString
diff --git a/Database/Redis/Internal.hs b/Database/Redis/Internal.hs
--- a/Database/Redis/Internal.hs
+++ b/Database/Redis/Internal.hs
@@ -13,6 +13,8 @@
 import qualified Data.ByteString.UTF8 as U
 import Data.Maybe (fromJust, isNothing, isJust)
 import Data.List (intersperse)
+import qualified Data.Map as Map
+import Data.Map (Map(..))
 import Control.Monad (when)
 import Control.Exception (block, bracket, bracketOnError, catch, SomeException)
 
@@ -24,7 +26,8 @@
 data RedisState = RedisState { server :: (String, String), -- ^ hostname and port pair
                                database :: Int,            -- ^ currently selected database
                                handle :: IO.Handle,        -- ^ real network connection
-                               isSubscribed :: Int         -- ^ currently in PUB/SUB mode
+                               isSubscribed :: Int,        -- ^ currently in PUB/SUB mode
+                               renamedCommands :: Map ByteString ByteString -- ^ map of the renamed commands
                              }
 
 -- | Redis connection descriptor
@@ -33,6 +36,12 @@
                     r_st       :: IORef RedisState}
              deriving Eq
 
+newRedis :: (String, String) -> IO.Handle -> IO Redis
+newRedis server h = do lcnt <- newMVar Nothing
+                       l <- newMVar ()
+                       st <- newIORef $ RedisState server 0 h 0 Map.empty
+                       return $ Redis lcnt l st
+
 -- | Redis command variants
 data Command = CInline ByteString
              | CMInline [ByteString]
@@ -65,18 +74,18 @@
     show (RInline s) = "RInline (" ++ (showbs s) ++ ")"
     show (RInt a) = "RInt " ++ show a
     show (RBulk (Just s)) = "RBulk " ++ showbs s
-    show (RBulk Nothing) = "RBulk Nothing"
+    show (RBulk Nothing) = "RBulk Nil"
     show (RMulti (Just rs)) = "RMulti [" ++ join rs ++ "]"
                               where join = concat . intersperse ", " . map show
     show (RMulti Nothing) = "RMulti Nil"
 
-data (BS s) => Message s = MSubscribe s Int
-                         | MUnsubscribe s Int
-                         | MPSubscribe s Int
-                         | MPUnsubscribe s Int
-                         | MMessage s s
-                         | MPMessage s s s
-                           deriving Show
+data (BS s) => Message s = MSubscribe s Int   -- ^ subscribed
+                         | MUnsubscribe s Int -- ^ unsubscribed
+                         | MPSubscribe s Int  -- ^ pattern subscribed
+                         | MPUnsubscribe s Int -- ^ pattern unsubscribed
+                         | MMessage s s        -- ^ message recieved
+                         | MPMessage s s s     -- ^ message recieved by pattern
+                           deriving (Eq, Show)
 
 urn       = U.fromString "\r\n"
 uspace    = U.fromString " "
@@ -154,34 +163,44 @@
 send h [] = return ()
 send h (bs:ls) = B.hPut h bs >> B.hPut h uspace >> send h ls
 
+lookupRenamed :: RedisState -> ByteString -> ByteString
+lookupRenamed r c = let c' = Map.findWithDefault c c (renamedCommands r)
+                    in if B.null c'
+                         then error $ "Command " ++ (fromBS c :: String) ++ " is disabled"
+                         else c'
+
 sendCommand :: RedisState -> Command -> IO ()
 sendCommand r (CInline bs) = let h = handle r
-                             in B.hPut h bs >> hPutRn h >> IO.hFlush h
-sendCommand r (CMInline ls) = let h = handle r
-                              in send h ls >> hPutRn h >> IO.hFlush h
-sendCommand r (CBulk lcmd bs) = let h = handle r
-                                    size = U.fromString $ show $ B.length bs
-                                in do send h lcmd
-                                      B.hPut h uspace
-                                      B.hPut h size
-                                      hPutRn h
-                                      B.hPut h bs
-                                      hPutRn h
-                                      IO.hFlush h
-sendCommand r (CMBulk strings) = let h = handle r
-                                     sendls [] = return ()
-                                     sendls (bs:ls) = let size = U.fromString . show . B.length
-                                                      in do B.hPut h ubucks
-                                                            B.hPut h $ size bs
-                                                            hPutRn h
-                                                            B.hPut h bs
-                                                            hPutRn h
-                                                            sendls ls
-                                 in do B.hPut h uasterisk
-                                       B.hPut h $ U.fromString $ show $ length strings
-                                       hPutRn h
-                                       sendls strings
-                                       IO.hFlush h
+                                 cmd = lookupRenamed r bs
+                             in B.hPut h cmd >> hPutRn h >> IO.hFlush h
+sendCommand r (CMInline (l:ls)) = let h = handle r
+                                      cmd = lookupRenamed r l
+                                  in send h (cmd:ls) >> hPutRn h >> IO.hFlush h
+sendCommand r (CBulk (l:ls) bs) = let h = handle r
+                                      size = U.fromString $ show $ B.length bs
+                                      cmd = lookupRenamed r l
+                                  in do send h (cmd:ls)
+                                        B.hPut h uspace
+                                        B.hPut h size
+                                        hPutRn h
+                                        B.hPut h bs
+                                        hPutRn h
+                                        IO.hFlush h
+sendCommand r (CMBulk s@(c:cs)) = let h = handle r
+                                      sendls [] = return ()
+                                      sendls (bs:ls) = let size = U.fromString . show . B.length
+                                                       in do B.hPut h ubucks
+                                                             B.hPut h $ size bs
+                                                             hPutRn h
+                                                             B.hPut h bs
+                                                             hPutRn h
+                                                             sendls ls
+                                      c' = lookupRenamed r c
+                                  in do B.hPut h uasterisk
+                                        B.hPut h $ U.fromString $ show $ length s
+                                        hPutRn h
+                                        sendls (c':cs)
+                                        IO.hFlush h
 
 sendCommand' = flip sendCommand
 
diff --git a/Database/Redis/Monad.hs b/Database/Redis/Monad.hs
--- a/Database/Redis/Monad.hs
+++ b/Database/Redis/Monad.hs
@@ -29,6 +29,7 @@
        R.Redis(..),
        R.Reply(..),
        R.Message(..),
+       R.LInsertDirection(..),
        R.Interval(..),
        R.IsInterval(..),
        R.SortOptions(..),
@@ -41,12 +42,13 @@
        -- * Database connection
         R.localhost, R.defaultPort,
        connect, disconnect, isConnected,
-       getServer, getDatabase,
+       getServer, getDatabase, renameCommand,
 
        -- * Redis commands
        -- ** Generic
-       ping, auth, quit, shutdown,
-       multi, exec, discard, run_multi, exists,
+       ping, auth, echo, quit, shutdown,
+       multi, exec, discard, run_multi,
+       watch, unwatch, run_cas, exists,
        del, getType, keys, randomKey, rename,
        renameNx, dbsize, expire, expireAt,
        persist, ttl, select, move, flushDb,
@@ -60,7 +62,8 @@
        strlen,
 
        -- ** Lists
-       rpush, lpush, llen, lrange, ltrim,
+       rpush, lpush, rpushx, lpushx,
+       llen, lrange, ltrim,
        lindex, lset, lrem, lpop, rpop,
        rpoplpush, blpop, brpop,
 
@@ -71,8 +74,8 @@
 
        -- ** Sorted sets
        zadd, zrem, zincrBy, zrange,
-       zrevrange, zrangebyscore, zcount,
-       zremrangebyscore, zcard, zscore,
+       zrevrange, zrangebyscore, zrevrangebyscore,
+       zcount, zremrangebyscore, zcard, zscore,
        zrank, zrevrank, zremrangebyrank,
        zunion, zinter, zunionStore, zinterStore,
 
@@ -99,6 +102,7 @@
 import Control.Monad.State (StateT(..))
 import Control.Applicative
 import Control.Monad.CatchIO
+import Data.ByteString (ByteString)
 
 import qualified Database.Redis.Redis as R
 import Database.Redis.Redis (IsInterval)
@@ -124,12 +128,19 @@
 getDatabase :: WithRedis m => m Int
 getDatabase = getRedis >>= liftIO . R.getDatabase
 
+renameCommand :: WithRedis m => ByteString -> ByteString -> m ()
+renameCommand c c' = do r <- getRedis
+                        liftIO $ R.renameCommand r c c'
+
 ping :: WithRedis m => m (R.Reply ())
 ping = getRedis >>= liftIO . R.ping
 
 auth :: WithRedis m => String -> m (R.Reply ())
 auth pwd = getRedis >>= liftIO . flip R.auth pwd
 
+echo :: (WithRedis m, BS s) => s -> m (R.Reply s)
+echo s = getRedis >>= liftIO . flip R.echo s
+
 quit :: WithRedis m => m ()
 quit = getRedis >>= liftIO . R.quit
 
@@ -145,16 +156,15 @@
 discard :: WithRedis m => m (R.Reply ())
 discard = getRedis >>= liftIO . R.discard
 
-run_multi :: (MonadCatchIO m, WithRedis m, BS s) => [m (R.Reply ())] -> m (R.Reply s)
-run_multi cs = let cs' = map (>>= R.noError) cs
-               in do r <- getRedis
-                     bracket (liftIO $ Internal.takeState r)
-                             (\_ -> liftIO $ Internal.putStateUnmodified r)
-                             (\rs -> do liftIO $ (Internal.sendCommand rs (Internal.CInline "MULTI") >> (Internal.recv rs :: IO (Internal.Reply ())))
-                                        sequence_ cs' `onException` (liftIO $ do Internal.sendCommand rs (Internal.CInline "DISCARD")
-                                                                                 Internal.recv rs :: IO (Internal.Reply ()))
-                                        liftIO $ Internal.sendCommand rs (Internal.CInline "EXEC")
-                                        liftIO $ Internal.recv rs)
+run_multi :: (MonadCatchIO m, WithRedis m, BS s) => m () -> m (R.Reply s)
+run_multi cs = do r <- getRedis
+                  bracket (liftIO $ Internal.takeState r)
+                          (\_ -> liftIO $ Internal.putStateUnmodified r)
+                          (\rs -> do liftIO $ (Internal.sendCommand rs (Internal.CInline "MULTI") >> (Internal.recv rs :: IO (Internal.Reply ())))
+                                     cs `onException` (liftIO $ do Internal.sendCommand rs (Internal.CInline "DISCARD")
+                                                                   Internal.recv rs :: IO (Internal.Reply ()))
+                                     liftIO $ Internal.sendCommand rs (Internal.CInline "EXEC")
+                                     liftIO $ Internal.recv rs)
 
 watch :: (WithRedis m, BS s) => [s] -> m (R.Reply ())
 watch cs = getRedis >>= liftIO . flip R.watch cs
@@ -162,7 +172,7 @@
 unwatch :: WithRedis m => m (R.Reply ())
 unwatch = getRedis >>= liftIO . R.unwatch
 
-run_cas :: (MonadCatchIO m, WithRedis m, BS s1, BS s2) => [s1] -> m (R.Reply s2) -> m (R.Reply s2)
+run_cas :: (MonadCatchIO m, WithRedis m, BS s1) => [s1] -> m a -> m a
 run_cas keys cs = let keys' = map toBS keys
                   in do r <- getRedis
                         bracket (liftIO $ Internal.takeState r)
@@ -296,6 +306,18 @@
 lpush key val = do r <- getRedis
                    liftIO $ R.lpush r key val
 
+rpushx :: (WithRedis m, BS s1, BS s2) => s1 -> s2 -> m (R.Reply Int)
+rpushx key val = do r <- getRedis
+                    liftIO $ R.rpushx r key val
+
+lpushx :: (WithRedis m, BS s1, BS s2) => s1 -> s2 -> m (R.Reply Int)
+lpushx key val = do r <- getRedis
+                    liftIO $ R.lpushx r key val
+
+linsert :: (WithRedis m, BS s1, BS s2, BS s3) => s1 -> R.LInsertDirection -> s2 -> s3 -> m (R.Reply Int)
+linsert key direction anchor value = do r <- getRedis
+                                        liftIO $ R.linsert r key direction anchor value
+
 llen :: (WithRedis m, BS s) => s -> m (R.Reply Int)
 llen key = getRedis >>= liftIO . flip R.llen key
 
@@ -329,11 +351,11 @@
 rpoplpush src dst = do r <- getRedis
                        liftIO $ R.rpoplpush r src dst
 
-blpop :: (WithRedis m, BS s1, BS s2) => [s1] -> Int -> m (R.Reply s2)
+blpop :: (WithRedis m, BS s1, BS s2) => [s1] -> Int -> m (Maybe (s1, s2))
 blpop keys timeout = do r <- getRedis
                         liftIO $ R.blpop r keys timeout
 
-brpop :: (WithRedis m, BS s1, BS s2) => [s1] -> Int -> m (R.Reply s2)
+brpop :: (WithRedis m, BS s1, BS s2) => [s1] -> Int -> m (Maybe (s1, s2))
 brpop keys timeout = do r <- getRedis
                         liftIO $ R.brpop r keys timeout
 
@@ -409,6 +431,10 @@
 zrangebyscore :: (WithRedis m, IsInterval i Double, BS s1, BS s2) => s1 -> i -> Maybe (Int, Int) -> Bool -> m (R.Reply s2)
 zrangebyscore key i limit withscores = do r <- getRedis
                                           liftIO $ R.zrangebyscore r key i limit withscores
+
+zrevrangebyscore :: (WithRedis m, IsInterval i Double, BS s1, BS s2) => s1 -> i -> Maybe (Int, Int) -> Bool -> m (R.Reply s2)
+zrevrangebyscore key i limit withscores = do r <- getRedis
+                                             liftIO $ R.zrevrangebyscore r key i limit withscores
 
 zcount :: (WithRedis m, IsInterval i Double, BS s) => s -> i -> m (R.Reply Int)
 zcount key limit = do r <- getRedis
diff --git a/Database/Redis/Redis.hs b/Database/Redis/Redis.hs
--- a/Database/Redis/Redis.hs
+++ b/Database/Redis/Redis.hs
@@ -29,6 +29,7 @@
        Redis,
        Reply(..),
        Message(..),
+       LInsertDirection(..),
        Interval(..),
        IsInterval(..),
        SortOptions(..),
@@ -40,11 +41,11 @@
        -- * Database connection
        localhost, defaultPort,
        connect, disconnect, isConnected,
-       getServer, getDatabase,
+       getServer, getDatabase, renameCommand,
 
        -- * Redis commands
        -- ** Generic
-       ping, auth, quit, shutdown,
+       ping, auth, echo,  quit, shutdown,
        multi, exec, discard, run_multi,
        watch, unwatch, run_cas, exists,
        del, getType, keys, randomKey, rename,
@@ -60,7 +61,8 @@
        strlen,
 
        -- ** Lists
-       rpush, lpush, llen, lrange, ltrim,
+       rpush, lpush, rpushx, lpushx,
+       linsert, llen, lrange, ltrim,
        lindex, lset, lrem, lpop, rpop,
        rpoplpush, blpop, brpop,
 
@@ -71,8 +73,8 @@
 
        -- ** Sorted sets
        zadd, zrem, zincrBy, zrange,
-       zrevrange, zrangebyscore, zcount,
-       zremrangebyscore, zcard, zscore,
+       zrevrange, zrangebyscore, zrevrangebyscore,
+       zcount, zremrangebyscore, zcard, zscore,
        zrank, zrevrank, zremrangebyrank,
        zunion, zinter, zunionStore, zinterStore,
 
@@ -102,6 +104,7 @@
 import Data.ByteString (ByteString)
 import Data.Maybe (fromJust, isNothing)
 import Data.List (intersperse)
+import qualified Data.Map as Map
 import Control.Monad (when)
 import Control.Exception (onException)
 
@@ -139,6 +142,10 @@
                     RBulk s    -> return s
                     _          -> error $ "wrong reply, RBulk expected: " ++ (show reply)
 
+-- | The same as fromRBulk but with fromJust applied
+fromRBulk' :: (Monad m, BS s) => Reply s -> m s
+fromRBulk' reply = fromRBulk reply >>= return . fromJust
+
 -- | Unwraps RMulti reply
 --
 -- Throws an exception when called with something different from RMulti
@@ -154,6 +161,10 @@
 fromRMultiBulk :: (Monad m, BS s) => Reply s -> m (Maybe [Maybe s])
 fromRMultiBulk reply = fromRMulti reply >>= return . (>>= sequence . map fromRBulk)
 
+-- | The same as fromRMultiBulk but with fromJust applied
+fromRMultiBulk' :: (Monad m, BS s) => Reply s -> m [s]
+fromRMultiBulk' reply = fromRMultiBulk reply >>= return . fromJust >>= return . (map fromJust)
+
 -- | Unwraps RInt reply
 --
 -- Throws an exception when called with something different from RInt
@@ -202,7 +213,9 @@
           mkpmsg  [RBulk (Just p), RBulk (Just c), RBulk (Just msg)] = MPMessage (fromBS p) (fromBS c) (fromBS msg)
 
 -- | Conects to Redis server and returns connection descriptor
-connect :: String -> String -> IO Redis
+connect :: String               -- ^ hostname or path to the redis socket
+        -> String               -- ^ port or null if unix sockets used
+        -> IO Redis
 connect hostname port =
     do s <- if null port
               then socket_unix hostname
@@ -211,10 +224,7 @@
        h <- S.socketToHandle s IO.ReadWriteMode
        IO.hSetBuffering h (IO.BlockBuffering Nothing)
 
-       lcnt <- newMVar Nothing
-       l <- newMVar ()
-       st <- newIORef $ RedisState (hostname, port) 0 h 0
-       return $ Redis lcnt l st
+       newRedis (hostname, port) h
 
 socket_inet :: String -> String -> IO S.Socket
 socket_inet hostname port =
@@ -248,6 +258,14 @@
 getDatabase :: Redis -> IO Int
 getDatabase = withState' (return . database)
 
+-- | Adds command to renaming map
+renameCommand :: Redis
+              -> ByteString        -- ^ command to rename
+              -> ByteString        -- ^ new name
+              -> IO ()
+renameCommand r c c' = inState_ r $ \rs -> let m = renamedCommands rs
+                                           in return $ rs {renamedCommands = Map.insert c c' m}
+
 {- ============ Just commands ============= -}
 -- | ping - pong
 --
@@ -255,6 +273,7 @@
 ping :: Redis -> IO (Reply ())
 ping = withState' (\rs -> sendCommand rs (CInline "PING") >> recv rs)
 
+{- UNTESTED -}
 -- | Password authentication
 --
 -- ROk returned
@@ -264,10 +283,21 @@
      -> IO (Reply ())
 auth r pwd = withState r (\rs -> sendCommand rs (CMInline ["AUTH", toBS pwd] ) >> recv rs)
 
+-- | Echo the given string
+--
+-- RBulk returned
+echo :: BS s =>
+        Redis
+     -> s                       -- ^ what to echo
+     -> IO (Reply s)
+echo r s = withState r (\rs -> sendCommand rs (CMBulk ["ECHO", toBS s]) >> recv rs)
+
+{- UNTESTED -}
 -- | Quit and close connection
 quit :: Redis -> IO ()
 quit r = withState r (sendCommand' (CInline "QUIT")) >> disconnect r
 
+{- UNTESTED -}
 -- | Stop all the clients, save the DB, then quit the server
 shutdown :: Redis -> IO ()
 shutdown r = withState r (sendCommand' (CInline "SHUTDOWN")) >> disconnect r
@@ -295,15 +325,14 @@
 -- RMulti returned - replys for all executed commands
 run_multi :: (BS s) =>
              Redis
-          -> [IO (Reply ())]    -- ^ IO actions to run
+          -> (Redis -> IO ())    -- ^ IO action to run
           -> IO (Reply s)
-run_multi r cs = let cs' = map (>>= noError) cs
-                 in withState r (\rs -> do sendCommand rs (CInline "MULTI")
-                                           (recv rs :: IO (Reply ())) >>= fromROk
-                                           (sequence_ cs') `onException` do sendCommand rs (CInline "DISCARD")
-                                                                            recv rs :: IO (Reply ())
-                                           sendCommand rs (CInline "EXEC")
-                                           recv rs)
+run_multi r cs = withState r (\rs -> do sendCommand rs (CInline "MULTI")
+                                        (recv rs :: IO (Reply ())) >>= fromROk
+                                        cs r `onException` do sendCommand rs (CInline "DISCARD")
+                                                              recv rs :: IO (Reply ())
+                                        sendCommand rs (CInline "EXEC")
+                                        recv rs)
 
 -- | Add keys to a watch list for Check-and-Set operation.
 --
@@ -331,18 +360,18 @@
 -- terminated with /unwatch/ command even if /exec/ command was sent.
 --
 -- Result of user-defined action returned
-run_cas :: (BS s1, BS s2) =>
+run_cas :: BS s1 =>
            Redis
-        -> [s1]                 -- ^ keys watched
-        -> IO (Reply s2)        -- ^ action to run
-        -> IO (Reply s2)
+        -> [s1]                     -- ^ keys watched
+        -> (Redis -> IO a)          -- ^ action to run
+        -> IO a
 run_cas r keys cs = let keys' = map toBS keys
                     in withState r (\rs -> do sendCommand rs (CMBulk ("WATCH" : keys'))
                                               (recv rs :: IO (Reply ())) >>= fromROk
-                                              res <- cs `onException` do sendCommand rs (CInline "DISCARD")
-                                                                         recv rs :: IO (Reply ())
-                                                                         sendCommand rs (CInline "UNWATCH")
-                                                                         recv rs :: IO (Reply ())
+                                              res <- cs r `onException` do sendCommand rs (CInline "DISCARD")
+                                                                           recv rs :: IO (Reply ())
+                                                                           sendCommand rs (CInline "UNWATCH")
+                                                                           recv rs :: IO (Reply ())
                                               sendCommand rs (CInline "UNWATCH")
                                               (recv rs :: IO (Reply ())) >>= fromROk
                                               return res)
@@ -386,7 +415,7 @@
 
 -- | Return random key name
 --
--- RInline returned
+-- RBulk returned
 randomKey :: BS s => Redis -> IO (Reply s)
 randomKey r = withState r (\rs -> sendCommand rs (CInline "RANDOMKEY") >> recv rs)
 
@@ -437,7 +466,7 @@
 expireAt :: BS s =>
             Redis
          -> s                   -- ^ target key
-         -> Int                 -- ^ timeout in seconds
+         -> Int                 -- ^ expiration time
          -> IO (Reply Int)
 expireAt r key timestamp = withState r (\rs -> sendCommand rs (CMBulk ["EXPIREAT", toBS key, toBS timestamp]) >> recv rs)
 
@@ -494,6 +523,7 @@
 flushAll :: Redis -> IO (Reply ())
 flushAll r = withState r (\rs -> sendCommand rs (CInline "FLUSHALL") >> recv rs)
 
+{- UNTESTED -}
 -- | Returns different information and statistics about the server
 --
 -- for more information see <http://code.google.com/p/redis/wiki/InfoCommand>
@@ -636,7 +666,10 @@
        -> IO (Reply Int)
 append r key str = withState r (\rs -> sendCommand rs (CMBulk ["APPEND", toBS key, toBS str]) >> recv rs)
 
--- | Get a substring. Indexes are zero-based.
+-- | Returns the substring of the string value stored at key,
+-- determined by the offsets start and end (both are
+-- inclusive). Negative offsets can be used in order to provide an
+-- offset starting from the end of the string.
 --
 -- RBulk returned
 substr :: (BS s1, BS s2) =>
@@ -655,7 +688,7 @@
        -> IO (Reply Int)
 strlen r key = withState r (\rs -> sendCommand rs (CMBulk ["STRLEN", toBS key]) >> recv rs)
 
--- | Add string value to the head of the list-type key. New list
+-- | Add string value to the tail of the list-type key. New list
 -- length returned
 --
 -- RInt returned
@@ -666,7 +699,7 @@
       -> IO (Reply Int)
 rpush r key val = withState r (\rs -> sendCommand rs (CMBulk ["RPUSH", toBS key, toBS val]) >> recv rs)
 
--- | Add string value to the tail of the list-type key. New list
+-- | Add string value to the head of the list-type key. New list
 -- length returned
 --
 -- RInt returned
@@ -677,6 +710,46 @@
       -> IO (Reply Int)
 lpush r key val = withState r (\rs -> sendCommand rs (CMBulk ["LPUSH", toBS key, toBS val]) >> recv rs)
 
+
+-- | Add string value to the head of existing list-type key.  New list
+-- length returned. If such a key was not exists, list is not created
+-- and (RInt 0) returned.
+--
+-- RInt returned
+lpushx :: (BS s1, BS s2) =>
+          Redis
+       -> s1                    -- ^ target key
+       -> s2                    -- ^ value to push
+       -> IO (Reply Int)
+lpushx r key val = withState r (\rs -> sendCommand rs (CMBulk ["LPUSHX", toBS key, toBS val]) >> recv rs)
+
+-- | Add string value to the tail of existing list-type key. New list
+-- length returned.  If such a key was not exists, list is not created
+-- and (RInt 0) returned.
+--
+-- RInt returned
+rpushx :: (BS s1, BS s2) =>
+          Redis
+       -> s1                    -- ^ target key
+       -> s2                    -- ^ value to push
+       -> IO (Reply Int)
+rpushx r key val = withState r (\rs -> sendCommand rs (CMBulk ["RPUSHX", toBS key, toBS val]) >> recv rs)
+
+data LInsertDirection = BEFORE | AFTER deriving Show
+
+-- | Inserts value in the list stored at key either before or after
+-- the reference value pivot.
+--
+-- RInt returned - resulting list length or (RInt -1) if target element was not found.
+linsert :: (BS s1, BS s2, BS s3) =>
+           Redis
+        -> s1                   -- ^ target list
+        -> LInsertDirection     -- ^ where to insert - before or after
+        -> s2                   -- ^ target element
+        -> s3                   -- ^ inserted value
+        -> IO (Reply Int)
+linsert r key direction anchor value = withState r (\rs -> sendCommand rs (CMBulk ["LINSERT", toBS key, toBS $ show direction, toBS anchor, toBS value]) >> recv rs)
+
 -- | Return lenght of the list. Note that for not-existing keys it
 -- returns zero length.
 --
@@ -787,8 +860,12 @@
          Redis
       -> [s1]                   -- ^ keys list
       -> Int                    -- ^ timeout
-      -> IO (Reply s2)
-blpop r keys timeout = withState r (\rs -> sendCommand rs (CMBulk (("BLPOP" : map toBS keys) ++ [toBS timeout])) >> recv rs)
+      -> IO (Maybe (s1, s2))
+blpop r keys timeout = withState r $ \rs -> do sendCommand rs (CMBulk (("BLPOP" : map toBS keys) ++ [toBS timeout]))
+                                               res <- recv rs >>= fromRMultiBulk
+                                               return $ case res of
+                                                          Nothing -> Nothing
+                                                          Just [Just k, Just v] -> Just (fromBS k, fromBS v)
 
 -- | Blocking rpop
 --
@@ -799,8 +876,12 @@
          Redis
       -> [s1]                   -- ^ keys list
       -> Int                    -- ^ timeout
-      -> IO (Reply s2)
-brpop r keys timeout = withState r (\rs -> sendCommand rs (CMBulk (("BRPOP" : map toBS keys) ++ [toBS timeout])) >> recv rs)
+      -> IO (Maybe (s1, s2))
+brpop r keys timeout = withState r $ \rs -> do sendCommand rs (CMBulk (("BRPOP" : map toBS keys) ++ [toBS timeout]))
+                                               res <- recv rs >>= fromRMultiBulk
+                                               return $ case res of
+                                                          Nothing -> Nothing
+                                                          Just [Just k, Just v] -> Just (fromBS k, fromBS v)
 
 -- | Add the specified member to the set value stored at key
 --
@@ -897,7 +978,7 @@
 -- | The same as 'sinter' but instead of being returned the resulting set
 -- is stored
 --
--- ROk returned
+-- RInt returned - resulting set cardinality.
 sinterStore :: (BS s1, BS s2) =>
                Redis
             -> s1               -- ^ where to store resulting set
@@ -918,7 +999,7 @@
 -- | The same as 'sunion' but instead of being returned the resulting set
 -- is stored
 --
--- ROk returned
+-- RInt returned - resulting set cardinality.
 sunionStore :: (BS s1, BS s2) =>
                Redis
             -> s1               -- ^ where to store resulting set
@@ -939,7 +1020,7 @@
 -- | The same as 'sdiff' but instead of being returned the resulting
 -- set is stored
 --
--- ROk returned
+-- RInt returned - resulting set cardinality.
 sdiffStore :: (BS s1, BS s2) =>
               Redis
            -> s1                -- ^ where to store resulting set
@@ -1083,6 +1164,26 @@
                                              i' = toInterval i
                                          in cmd `seq` withState r (\rs -> sendCommand rs (CMBulk cmd) >> recv rs)
 
+-- | Return the all the elements in the sorted set with a score that
+-- lays within a given interval. Elements is ordered from greater
+-- score to lower. Interval passed into command must be reversed
+-- (first value is greater then second)
+--
+-- RMulti filled with RBulk returned
+zrevrangebyscore :: (IsInterval i Double, BS s1, BS s2) =>
+                    Redis
+                 -> s1
+                 -> i
+                 -> Maybe (Int, Int)
+                 -> Bool
+                 -> IO (Reply s2)
+zrevrangebyscore r key i limit withscores = let cmd' = i' `seq` ["ZREVRANGEBYSCORE", toBS key, toBS (from i'), toBS (to i')]
+                                                cmd'' = maybe cmd' (\(a, b) -> cmd' ++ ["LIMIT", toBS a, toBS b]) limit
+                                                cmd | withscores = cmd'' ++ ["WITHSCORES"]
+                                                    | otherwise  = cmd''
+                                                i' = toInterval i
+                                            in cmd `seq` withState r (\rs -> sendCommand rs (CMBulk cmd) >> recv rs)
+
 -- | Count a number of elements of the sorted set with a score that
 -- lays within a given interval
 --
@@ -1129,23 +1230,25 @@
        -> IO (Reply s3)
 zscore r key member = withState r (\rs -> sendCommand rs (CMBulk ["ZSCORE", toBS key, toBS member]) >> recv rs)
 
--- | Returns sorted set element sequence number counting from zero
+-- | Returns the rank of member in the sorted set stored at key, with
+-- the scores ordered from low to high.
 --
--- RInt returned
+-- RInt returned or (RBulk Nothing) if value is not found in set.
 zrank :: (BS s1, BS s2) =>
          Redis
-      -> s1
-      -> s2
+      -> s1                     -- ^ target key
+      -> s2                     -- ^ value
       -> IO (Reply Int)
 zrank r key member = withState r (\rs -> sendCommand rs (CMBulk ["ZRANK", toBS key, toBS member]) >> recv rs)
 
--- | Returns sorted set element sequence number for reversed sort order
+-- | Returns the rank of member in the sorted set stored at key, with
+-- the scores ordered from high to low.
 --
--- RInt returned
+-- RInt returned or (RBulk Nothing) if value is not found in set.
 zrevrank :: (BS s1, BS s2) =>
             Redis
-         -> s1
-         -> s2
+         -> s1                  -- ^ target key
+         -> s2                  -- ^ value
          -> IO (Reply Int)
 zrevrank r key member = withState r (\rs -> sendCommand rs (CMBulk ["ZREVRANK", toBS key, toBS member]) >> recv rs)
 
@@ -1218,7 +1321,7 @@
 
         aggr_s | aggregate == SUM = []
                | otherwise        = ["AGGREGATE", toBS (show aggregate)]
-    in withState r (\rs -> sendCommand rs (CMBulk (("ZINTER" : toBS dst : src_s) ++ weight_s ++ aggr_s)) >> recv rs)
+    in withState r (\rs -> sendCommand rs (CMBulk (("ZINTERSTORE" : toBS dst : src_s) ++ weight_s ++ aggr_s)) >> recv rs)
 
 {-# DEPRECATED zinter "ZINTER command was renamed to ZINTERSTORE" #-}
 zinter :: (BS s1, BS s2) => Redis -> s1 -> [s2] -> [Double] -> Aggregate -> IO (Reply Int)
@@ -1329,10 +1432,10 @@
 -- | Return all the field names and associated values the hash holding
 -- in form of /[field1, value1, field2, value2...]/
 --
--- RMulti field with RBulk returned
+-- RMulti field with RBulk returned. If key doesn't exists (RMulti []) returned.
 hgetall :: (BS s1, BS s2) =>
            Redis
-        -> s1
+        -> s1                   -- ^ target key
         -> IO (Reply s2)
 hgetall r key = withState r (\rs -> sendCommand rs (CMBulk ["HGETALL", toBS key]) >> recv rs)
 
@@ -1504,24 +1607,28 @@
                                                   then recv rs >>= parseMessage >>= return . Just
                                                   else return Nothing
 
+{- UNTESTED -}
 -- | Save the whole dataset on disk
 --
 -- ROk returned
 save :: Redis -> IO (Reply ())
 save r = withState r (\rs -> sendCommand rs (CInline "SAVE") >> recv rs)
 
+{- UNTESTED -}
 -- | Save the DB in background
 --
 -- ROk returned
 bgsave :: Redis -> IO (Reply ())
 bgsave r = withState r (\rs -> sendCommand rs (CInline "BGSAVE") >> recv rs)
 
+{- UNTESTED -}
 -- | Return the UNIX TIME of the last DB save executed with success
 --
 -- RInt returned
 lastsave :: Redis -> IO (Reply Int)
 lastsave r = withState r (\rs -> sendCommand rs (CInline "LASTSAVE") >> recv rs)
 
+{- UNTESTED -}
 -- | Rewrites the Append Only File in background
 --
 -- ROk returned
diff --git a/Test.hs b/Test.hs
new file mode 100644
--- /dev/null
+++ b/Test.hs
@@ -0,0 +1,46 @@
+module Test where
+
+import System.Environment
+import Test.HUnit
+import Test.Setup
+import qualified Test.Connection as Connection
+import qualified Test.GenericCommands as GenericCommands
+import qualified Test.StringCommands as StringCommands
+import qualified Test.ListCommands as ListCommands
+import qualified Test.SetCommands as SetCommands
+import qualified Test.ZSetCommands as ZSetCommands
+import qualified Test.HashCommands as HashCommands
+import qualified Test.SortCommands as SortCommands
+import qualified Test.PubSubCommands as PubSubCommands
+import qualified Test.MultiCommands as MultiCommands
+import qualified Test.CASCommands as CASCommands
+import qualified Test.Monad.MultiCommands as M_MultiCommands
+import qualified Test.Monad.CASCommands as M_CASCommands
+import qualified Test.Lock as Lock
+
+tests = [("connection", TestLabel "Connection" Connection.tests),
+         ("generic", TestLabel "Generic commands" GenericCommands.tests),
+         ("string", TestLabel "String commands" StringCommands.tests),
+         ("list", TestLabel "List commands" ListCommands.tests),
+         ("set", TestLabel "Set commands" SetCommands.tests),
+         ("zset", TestLabel "Sorted set commands" ZSetCommands.tests),
+         ("hash", TestLabel "Hash commands" HashCommands.tests),
+         ("sort", TestLabel "Sort comands" SortCommands.tests),
+         ("pubsub", TestLabel "Pub/Sub commands" PubSubCommands.tests),
+         ("multi", TestLabel "Multi commands" MultiCommands.tests),
+         ("cas", TestLabel "CAS commands" CASCommands.tests),
+         ("multi", TestLabel "Multi commands within monad wrapper" M_MultiCommands.tests),
+         ("cas", TestLabel "CAS commands within monad wrapper" M_CASCommands.tests),
+         ("lock", TestLabel "Lock" Lock.tests)]
+
+mkTests Nothing = TestList $ map snd tests
+mkTests (Just label) = TestList $ map snd $ filter ((== label) . fst) tests
+
+main :: IO ()
+main = do args <- getArgs
+          if (length args < 1)
+             then error "Usage: runhaskell Test.hs <path-to-redis-binary> [label]"
+             else startRedis $ head args
+          let label = if length args < 2 then Nothing else (Just $ args !! 1)
+          runTestTT $ mkTests label
+          shutdownRedis
diff --git a/Test/CASCommands.hs b/Test/CASCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/CASCommands.hs
@@ -0,0 +1,82 @@
+module Test.CASCommands where
+
+import Control.Monad.Reader
+import Control.Exception
+import Control.Concurrent (threadDelay)
+import Control.Concurrent.MVar
+import Test.HUnit
+import Database.Redis.Redis
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "watch/exec successful" test_watch_success,
+                  TestLabel "wath/exec fail" test_watch_fail,
+                  TestLabel "run_cas" test_run_cas,
+                  TestLabel "run_cas that raised an exception" test_run_cas_exception]
+
+test_watch_success = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do watch r ["foo", "bar"]
+                   Just foo <- get r "foo" >>= fromRBulk
+                   multi r
+                   set r "foo" "bar" >>= noError
+                   set r "foo_" foo >>= noError
+                   exec r :: IO (Reply ())
+                   foo_ <- get r "foo_" >>= fromRBulk :: IO (Maybe String)
+                   assertEqual "" (Just foo) foo_
+                   get r "foo" >>= fromRBulk >>= assertEqual "" (Just "bar")
+
+test_watch_fail = TestCase $ testRedis2 $
+    do r1 <- ask
+       r2 <- ask2
+       addStr
+       liftIO $ do watch r1 ["foo", "bar"]
+                   Just foo <- get r1 "foo" >>= fromRBulk :: IO (Maybe String)
+                   multi r1
+                   set r2 "foo" "baz" >>= fromROk
+                   set r1 "foo" "bar" >>= noError -- note that this statement will not executed
+                   set r1 "foo_" foo >>= noError
+                   exec r1 :: IO (Reply ())
+                   foo_ <- get r1 "foo_" >>= fromRBulk :: IO (Maybe String)
+                   assertEqual "" Nothing foo_
+                   get r1 "foo" >>= fromRBulk >>= assertEqual "" (Just "baz")
+
+test_run_cas = TestCase $ testRedis2 $
+    let act :: Redis -> IO String
+        act r = do Just foo <- get r "foo" >>= fromRBulk
+                   multi r
+                   threadDelay 1000
+                   set r "foo" "bar" >>= noError
+                   set r "foo_" foo >>= noError
+                   exec r :: IO (Reply ())
+                   return foo
+
+    in do r1 <- ask
+          r2 <- ask2
+          addStr
+          liftIO $ do foo <- run_cas r1 ["foo", "bar"] act
+                      foo_ <- get r1 "foo_" >>= fromRBulk :: IO (Maybe String)
+                      assertEqual "" (Just foo) foo_
+                      get r1 "foo" >>= fromRBulk >>= assertEqual "" (Just "bar")
+
+                      del r1 "foo_"
+                      later 500 $ set r2 "foo" "baz"
+                      run_cas r1 ["foo", "bar"] act
+                      foo_ <- get r1 "foo_" >>= fromRBulk :: IO (Maybe String)
+                      assertEqual "" Nothing foo_
+                      get r1 "foo" >>= fromRBulk >>= assertEqual "" (Just "baz")
+
+test_run_cas_exception = TestCase $ testRedis $
+    let act :: Redis -> IO ()
+        act r = do multi r
+                   set r "foo" "bar" >>= noError
+                   set r "bar" "foo" >>= noError
+                   error "bang!"
+                   exec r :: IO (Reply ())
+                   return ()
+    in do r <- ask
+          addStr
+          liftIO $ do foo <- get r "foo" :: IO (Reply String)
+                      assertRaises "" (ErrorCall undefined) $ run_cas r ["foo", "bar"] act
+                      get r "foo" >>= assertEqual "" foo
diff --git a/Test/Connection.hs b/Test/Connection.hs
new file mode 100644
--- /dev/null
+++ b/Test/Connection.hs
@@ -0,0 +1,28 @@
+module Test.Connection where
+
+import Control.Monad.Reader
+import Test.HUnit
+import Database.Redis.Redis
+
+import Test.Setup
+
+tests = TestList [TestLabel "connect" test_connect,
+                  TestLabel "connection state" test_connection_state]
+
+test_connect = TestCase $ do r <- connect localhost defaultPort
+                             connected <- isConnected r
+                             assertBool "Connection failed for some reason" connected
+                             disconnect r
+                             connected <- isConnected r
+                             assertBool "Disconnected but still in 'connected' state" $ not connected
+
+test_connection_state = TestCase $ testRedis $
+    do r <- ask
+       liftIO $ do server <- getServer r
+                   assertEqual "Wrong server in connection state" (localhost, defaultPort) server
+                   select r 0
+                   db <- getDatabase r
+                   assertEqual "Wrond database number in connection state" 0 db
+                   select r 1
+                   db <- getDatabase r
+                   assertEqual "Wrond database number in connection state" 1 db
diff --git a/Test/GenericCommands.hs b/Test/GenericCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/GenericCommands.hs
@@ -0,0 +1,165 @@
+module Test.GenericCommands where
+
+import Control.Monad.Reader
+import Control.Exception
+import Test.HUnit
+import Data.Maybe
+import System.Time
+import Database.Redis.Redis
+import Database.Redis.ByteStringClass
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "from*" test_unwrap_reply,
+                  TestLabel "ping and renamed command" test_ping,
+                  TestLabel "echo" test_echo,
+                  TestLabel "exists and del" test_exists_del,
+                  TestLabel "getType" test_get_type,
+                  TestLabel "keys" test_keys,
+                  TestLabel "randomKey" test_random_key,
+                  TestLabel "rename" test_rename,
+                  TestLabel "renameNx" test_renameNx,
+                  TestLabel "dbsize" test_dbsize,
+                  TestLabel "expire" test_expire,
+                  TestLabel "ttl and persist" test_ttl_persist,
+                  TestLabel "move" test_move,
+                  TestLabel "flushDb" test_flushDb,
+                  TestLabel "flushAll" test_flushAll]
+
+test_unwrap_reply = TestCase $ let inline = return $ RInline "foo"
+                                   int = return $ RInt 1 :: IO (Reply String)
+                                   bulk = return $ RBulk $ Just "foo"                                          
+                                   mbulk = return $ RMulti $ Just [RBulk (Just "foo"), RBulk Nothing]
+                                   mbulk' = return $ RMulti Nothing :: IO (Reply String)
+                                   err = return $ RError "foo" :: IO (Reply String)
+                               in do inline >>= fromRInline >>= assertEqual "" "foo"
+                                     int >>= fromRInt >>= assertEqual "" 1
+                                     bulk >>= fromRBulk >>= assertEqual "" (Just "foo")
+                                     mbulk >>= fromRMulti >>= assertEqual "" (Just [RBulk (Just "foo"), RBulk Nothing])
+                                     mbulk' >>= fromRMulti >>= assertEqual "" Nothing
+                                     mbulk >>= fromRMultiBulk >>= assertEqual "" (Just [Just "foo", Nothing])
+                                     mbulk' >>= fromRMultiBulk >>= assertEqual "" Nothing
+                                     mbulk >>= noError >>= assertEqual "" ()
+                                     assertRaises "" (ErrorCall undefined) (err >>= fromRInline)
+                                     assertRaises "" (ErrorCall undefined) (inline >>= fromRBulk)
+
+test_ping = TestCase $ testRedis $
+    do r <- ask
+       liftIO $ do renameCommand r (toBS "PING") (toBS "P") -- also tests "renamed command" feature
+                   repl <- ping r
+                   assertEqual "" RPong repl
+
+test_echo = TestCase $ testRedis $
+    do r <- ask
+       liftIO $ echo r "foobar" >>= fromRBulk >>= assertEqual "" (Just "foobar")
+
+test_exists_del = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do e <- exists r "foo" >>= fromRInt
+                   assertEqual "foo exists" 1 e
+                   del r "foo"
+                   e <- exists r "foo" >>= fromRInt
+                   assertEqual "foo was deleted" 0 e
+
+test_get_type = TestCase $ testRedis $
+    do addAll
+       mapM_ (uncurry checkType) [("foo", "string"),
+                                  ("bar", "string"),
+                                  ("list", "list"),
+                                  ("set", "set"),
+                                  ("zset", "zset"),
+                                  ("hash", "hash"),
+                                  ("no-such-key", "none")]
+    where checkType key t = do r <- ask
+                               liftIO $ do t' <- getType r key >>= fromRInline
+                                           assertEqual "" t t'
+
+test_keys = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       addSet
+       addZSet
+       liftIO $ do res <- keys r "nokey" >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   assertEqual "There's no key named \"nokey\"" (Just []) res
+                   res <- keys r "foo" >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   assertEqual "One key named \"foo\"" (Just [Just "foo"]) res
+                   res <- keys r "*set" >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   assertEqual "set and zset keys selected" (Just [Just "set", Just "zset"]) res
+
+-- that seems randomKey always returns the same sequence of keys
+test_random_key = TestCase $ testRedis $
+    do r <- ask
+       addAll
+       liftIO $ do res <- randomKey r >>= fromRBulk >>= return . fromJust
+                   assertOneOf "" ["foo", "bar", "list", "set", "zset", "hash"] res
+
+test_rename = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do exists r "zoo" >>= fromRInt >>= assertEqual "there is no zoo here!" 0
+                   foo <- get r "foo" :: IO (Reply String)
+                   rename r "foo" "zoo" >>= fromROk
+                   exists r "foo" >>= fromRInt >>= assertEqual "foo was renamed to zoo" 0
+                   exists r "zoo" >>= fromRInt >>= assertEqual "foo was renamed to zoo" 1
+                   get r "zoo" >>= assertEqual "" foo
+
+test_renameNx = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do renameNx r "foo" "bar" >>= fromRInt >>= assertEqual "" 0
+                   exists r "foo" >>= fromRInt >>= assertEqual "" 1
+
+test_dbsize = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do res <- dbsize r >>= fromRInt
+                   real <- (keys r "*" :: IO (Reply String)) >>= fromRMultiBulk >>= return . length . fromJust
+                   assertEqual "" real res
+
+test_expire = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do expire r "foo" 0 >>= fromRInt >>= assertEqual "" 1
+                   exists r "foo" >>= fromRInt >>= assertEqual "foo has to be expired now" 0
+                   TOD now _ <- getClockTime
+                   expireAt r "bar" $ fromIntegral now
+                   exists r "bar" >>= fromRInt >>= assertEqual "bar has to be expired now" 0
+
+test_ttl_persist = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do ttl r "foo" >>= fromRInt >>= assertEqual "" (-1)
+                   expire r "foo" 30 >>= noError
+                   ttl r "foo" >>= fromRInt >>= assertBool "TTL is greater then expiration timeout" . (<= 30)
+                   persist r "foo" >>= noError
+                   ttl r "foo" >>= fromRInt >>= assertEqual "foo was persisted again" (-1)
+
+test_move = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do exists r "foo" >>= fromRInt >>= assertEqual "foo has to be in first database" 1
+                   move r "foo" 1
+                   exists r "foo" >>= fromRInt >>= assertEqual "now foo is in second database" 0
+                   select r 1
+                   exists r "foo" >>= fromRInt >>= assertEqual "now foo is in second database" 1
+
+test_flushDb = TestCase $ testRedis $
+    do r <- ask
+       liftIO $ do select r 1
+                   set r "leave_it_alone" "hooray!"
+                   select r 0
+                   flushDb r
+                   dbsize r >>= fromRInt >>= assertEqual "first database was flushed" 0
+                   select r 1
+                   dbsize r >>= fromRInt >>= assertEqual "but not the second one" 1
+
+test_flushAll = TestCase $ testRedis $
+    do r <- ask
+       liftIO $ do select r 1
+                   set r "baz" "no way"
+                   select r 0
+                   flushAll r
+                   dbsize r >>= fromRInt >>= assertEqual "first database was flushed" 0
+                   select r 1
+                   dbsize r >>= fromRInt >>= assertEqual "and the second database too" 0
diff --git a/Test/HashCommands.hs b/Test/HashCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/HashCommands.hs
@@ -0,0 +1,101 @@
+module Test.HashCommands where
+
+import Test.HUnit
+import Control.Monad.Reader
+import Data.Maybe
+import Data.List
+import Data.Map (Map(..), (!))
+import qualified Data.Map as M
+import qualified Data.Set as S
+import Database.Redis.Redis hiding (sort)
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "hgetall" test_hgetall,
+                  TestLabel "hkeys" test_hkeys,
+                  TestLabel "hvals" test_hvals,
+                  TestLabel "hlen" test_hlen,
+                  TestLabel "hexists, hset, hget and hdel" test_hexists_hset_hget_hdel,
+                  TestLabel "hmset and hmget" test_hmset_hmget,
+                  TestLabel "hincrby" test_hincrby]
+
+asHash :: Reply String -> IO (Map String String)
+asHash r = fromRMultiBulk r >>= return . M.fromList . build . map fromJust . fromJust
+    where build (a:b:z) = (a, b) : build z
+          build (a:[]) = error "unpaired element"
+          build [] = []
+
+test_hgetall = TestCase $ testRedis $
+    let expected = M.fromList $ zip ["foo", "bar", "baz"] ["1", "2", "3"]
+    in do r <- ask
+          addHash
+          liftIO $ do h <- hgetall r "hash" >>= asHash
+                      assertEqual "" expected h
+                      h <- hgetall r "no-such-key" >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                      assertEqual "" (Just []) h
+
+test_hkeys = TestCase $ testRedis $
+    do r <- ask
+       addHash
+       liftIO $ do h <- hgetall r "hash" >>= asHash
+                   let expected = M.keys h
+                   k <- hkeys r "hash" >>= fromRMultiBulk >>= return . sort . map fromJust . fromJust
+                   assertEqual "" expected k
+                   k <- hkeys r "no-such-key" >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   assertEqual "" (Just []) k
+
+test_hvals = TestCase $ testRedis $
+    do r <- ask
+       addHash
+       liftIO $ do h <- hgetall r "hash" >>= asHash
+                   let expected = S.fromList $ M.elems h -- Data.Set for order-independent equality
+                   vals <- hvals r "hash" >>= fromRMultiBulk >>= return . S.fromList . map fromJust . fromJust
+                   assertEqual "" expected vals
+                   k <- hkeys r "no-such-key" >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   assertEqual "" (Just []) k
+
+test_hlen = TestCase $ testRedis $
+    do r <- ask
+       addHash
+       liftIO $ do h <- hgetall r "hash" >>= asHash
+                   l <- hlen r "hash" >>= fromRInt
+                   assertEqual "" (M.size h) l
+                   l <- hlen r "no-such-key" >>= fromRInt
+                   assertEqual "" 0 l
+
+test_hexists_hset_hget_hdel = TestCase $ testRedis $
+    do r <- ask
+       addHash
+       liftIO $ do hexists r "hash" "foo" >>= fromRInt >>= assertEqual "" 1
+                   hexists r "hash" "jaz" >>= fromRInt >>= assertEqual "" 0
+                   hdel r "hash" "foo" >>= fromRInt >>= assertEqual "" 1
+                   hdel r "hash" "jaz" >>= fromRInt >>= assertEqual "" 0
+                   hexists r "hash" "foo" >>= fromRInt >>= assertEqual "" 0
+                   hset r "hash" "foo" "1" >>= fromRInt >>= assertEqual "" 1
+                   hexists r "hash" "foo" >>= fromRInt >>= assertEqual "" 1
+                   hset r "hash" "foo" "bar" >>= fromRInt >>= assertEqual "" 0
+                   hget r "hash" "foo" >>= fromRBulk >>= assertEqual "" (Just "bar")
+                   hget r "hash" "jaz" >>= fromRBulk >>= assertEqual "" (Nothing :: Maybe String)
+
+test_hmset_hmget = TestCase $ testRedis $
+    do r <- ask
+       addHash
+       liftIO $ do h <- hgetall r "hash" >>= asHash
+                   h' <- hmget r "hash" ["foo", "bar"] >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   let h'' = M.fromList $ zip ["foo", "bar"] h'
+                   assertEqual "" h (M.union h'' h)
+                   hmset r "hash" [("foo", "foo"), ("bar", "bar")] >>= noError
+                   let expected = M.update (const $ Just "foo") "foo" $ M.update (const $ Just "bar") "bar" h
+                   h' <- hgetall r "hash" >>= asHash
+                   assertEqual "" expected h'
+
+test_hincrby = TestCase $ testRedis $
+    do r <- ask
+       addHash
+       liftIO $ do h <- hgetall r "hash" >>= asHash >>= return . M.map read :: IO (Map String Int)
+                   res <- hincrby r "hash" "foo" 2 >>= fromRInt
+                   assertEqual "" (h ! "foo" + 2) res
+                   hget r "hash" "foo" >>= fromRBulk >>= assertEqual "" (Just res)
+                   res <- hincrby r "hash" "foo" (-2) >>= fromRInt
+                   assertEqual "" (h ! "foo") res
+                   hget r "hash" "foo" >>= fromRBulk >>= assertEqual "" (Just res)
diff --git a/Test/ListCommands.hs b/Test/ListCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/ListCommands.hs
@@ -0,0 +1,170 @@
+module Test.ListCommands where
+
+import Prelude hiding ((!!))
+import Control.Monad.Reader
+import Data.List
+import Test.HUnit
+import Data.Maybe
+import Database.Redis.Redis
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "lrange" test_lrange,
+                  TestLabel "llen" test_llen,
+                  TestLabel "lpush and rpush" test_lpush_rpush,
+                  TestLabel "lpushx and rpushx" test_lpushx_rpushx,
+                  TestLabel "linsert" test_linsert,
+                  TestLabel "ltrim" test_ltrim,
+                  TestLabel "lindex and lset" test_lindex_lset,
+                  TestLabel "lrem" test_lrem,
+                  TestLabel "lpop and rpop" test_lpor_rpop,
+                  TestLabel "rpoplpush" test_rpoplpush,
+                  TestLabel "blpop" test_blpop,
+                  TestLabel "brpop" test_brpop]
+
+test_lrange = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do list <- lrange r "list" takeAll >>= fromRMultiBulk
+                   assertEqual "" (Just [Just "1", Just "2", Just "3"]) list
+                   list <- lrange r "list" (1, 1) >>= fromRMultiBulk
+                   assertEqual "" (Just [Just "2"]) list
+
+test_llen = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do len <- llen r "list" >>= fromRInt
+                   Just list <- lrange r "list" takeAll >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   assertEqual "equals to real list length" (length list) len
+                   len <- llen r "no-such-key" >>= fromRInt
+                   assertEqual "not existing key is the same as an empty list" 0 len
+
+test_lpush_rpush = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do Just list <- lrange r "list" takeAll >>= fromRMultiBulk
+                   len <- rpush r "list" "4" >>= fromRInt
+                   assertEqual "list length was increased by 1" (length list + 1) len
+                   Just list' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   assertEqual "" (list ++ [(Just "4")]) list'
+
+                   len <- lpush r "list" "0" >>= fromRInt
+                   assertEqual "list length was increased by 1 again" (length list' + 1) len
+                   Just list'' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   assertEqual "" ((Just "0") : list') list''
+
+test_lpushx_rpushx = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do Just list <- lrange r "list" takeAll >>= fromRMultiBulk
+                   len <- rpushx r "list" "4" >>= fromRInt
+                   assertEqual "list length was increased by 1" (length list + 1) len
+                   Just list' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   assertEqual "" (list ++ [(Just "4")]) list'
+
+                   len <- lpushx r "list" "0" >>= fromRInt
+                   assertEqual "list length was increased by 1 again" (length list' + 1) len
+                   Just list'' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   assertEqual "" ((Just "0") : list') list''
+
+                   lpushx r "no-such-key" "1" >>= fromRInt >>= assertEqual "List was not created" 0
+                   exists r "no-such-key" >>= fromRInt >>= assertEqual "List was not created" 0
+                   rpushx r "no-such-key" "1" >>= fromRInt >>= assertEqual "List was not created" 0
+                   exists r "no-such-key" >>= fromRInt >>= assertEqual "List was not created" 0
+
+test_linsert = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do Just list <- lrange r "list" takeAll >>= fromRMultiBulk
+                   len <- linsert r "list" BEFORE "2" "foo" >>= fromRInt
+                   assertEqual "list length was increased by 1" (length list + 1) len
+                   Just list' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   let excepted = let (a,b) = span (/= Just "2") list
+                                  in a ++ ((Just "foo") : b)
+                   assertEqual "" excepted list'
+
+                   len <- linsert r "list" AFTER "foo" "bar" >>= fromRInt
+                   assertEqual "list length was increased by 1" (length list' + 1) len
+                   Just list'' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   let excepted = let (a,b) = span (/= Just "foo") list'
+                                  in a ++ (head b : Just "bar" : tail b)
+                   assertEqual "" excepted list''
+
+test_ltrim = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do Just list <- lrange r "list" takeAll >>= fromRMultiBulk :: (IO (Maybe [Maybe String]))
+                   ltrim r "list" (1, 1) >>= fromROk
+                   Just list' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   assertEqual "" (take 1 $ drop 1 $ list) list'
+
+test_lindex_lset = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do Just list <- lrange r "list" takeAll >>= fromRMultiBulk :: (IO (Maybe [Maybe String]))
+                   el <- lindex r "list" 1 >>= fromRBulk
+                   assertEqual "" (list !! 1) el
+                   lset r "list" 1 "4" >>= fromROk
+                   lindex r "list" 1 >>= fromRBulk >>= assertEqual "" (Just "4")
+
+test_lrem = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do Just list <- lrange r "list" takeAll >>= fromRMultiBulk
+                   lrem r "list" 1 (fromJust $ list !! 1) >>= fromRInt >>= assertEqual ("delete only first value equals to " ++ (fromJust $ list !! 1)) 1
+                   Just list' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   assertEqual "" ((list !! 1) `delete` list) list'
+
+test_lpor_rpop = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do Just list <- lrange r "list" takeAll >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   lpop r "list" >>= fromRBulk >>= assertEqual "" (head list)
+                   rpop r "list" >>= fromRBulk >>= assertEqual "" (last list)
+                   Just list' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   assertEqual "" (init $ tail list) list'
+
+test_rpoplpush = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ do Just list <- lrange r "list" takeAll >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   rpoplpush r "list" "list2" >>= fromRBulk >>= assertEqual "" (last list)
+                   rpoplpush r "list" "list2" >>= fromRBulk >>= assertEqual "" (last $ init list)
+                   Just list' <- lrange r "list" takeAll >>= fromRMultiBulk
+                   assertEqual "Two elemens was rpopped" (init $ init list) list'
+                   Just list'' <- lrange r "list2" takeAll >>= fromRMultiBulk
+                   assertEqual "Two elemens was lpushed" (reverse $ take 2 $ reverse list) list''
+
+test_blpop = TestCase $ testRedis2 $
+    do r1 <- ask
+       r2 <- ask2
+       addList
+       liftIO $ do Just list <- lrange r1 "list" takeAll >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   res <- blpop r1 ["bar", "list"] 1
+                   assertEqual "" (Just ("list", fromJust $ head list)) res
+                   Just list' <- lrange r1 "list" takeAll >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   assertEqual "" (tail list) list'
+
+                   later 500 $ lpush r2 "zap" "1" >>= noError
+                   res <- blpop r1 ["zap"] 2
+                   assertEqual "" (Just ("zap", "1")) res
+                   exists r1 "zap" >>= fromRInt >>= assertEqual "" 0
+
+                   (blpop r1 ["zap"] 1 :: IO (Maybe (String, String))) >>= assertEqual "" Nothing
+
+test_brpop = TestCase $ testRedis2 $
+    do r1 <- ask
+       r2 <- ask2
+       addList
+       liftIO $ do Just list <- lrange r1 "list" takeAll >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   res <- brpop r1 ["bar", "list"] 1
+                   assertEqual "" (Just ("list", fromJust $ last list)) res
+                   Just list' <- lrange r1 "list" takeAll >>= fromRMultiBulk :: IO (Maybe [Maybe String])
+                   assertEqual "" (init list) list'
+
+                   later 500 $ lpush r2 "zap" "1" >>= noError
+                   res <- brpop r1 ["zap"] 2
+                   assertEqual "" (Just ("zap", "1")) res
+                   exists r1 "zap" >>= fromRInt >>= assertEqual "" 0
+
+                   (brpop r1 ["zap"] 1 :: IO (Maybe (String, String))) >>= assertEqual "" Nothing
diff --git a/Test/Lock.hs b/Test/Lock.hs
new file mode 100644
--- /dev/null
+++ b/Test/Lock.hs
@@ -0,0 +1,36 @@
+module Test.Lock where
+
+import Control.Monad.Reader
+import Control.Exception
+import Control.Concurrent.MVar
+import Test.HUnit
+import Data.Maybe
+import System.Time
+import Database.Redis.Redis
+import Database.Redis.Utils.Lock
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "acquire and release" test_acquire_release,
+                  TestLabel "acquire timeout" test_acquire_timeout]
+
+test_acquire_release = TestCase $ testRedis2 $
+    do r1 <- ask
+       r2 <- ask2
+       liftIO $ do acquire r1 "lock" 1000 50 >>= assertBool ""
+                   acquire r2 "lock" 1000 50 >>= assertEqual "" False
+                   release r1 "lock"
+                   acquire r2 "lock" 1000 50 >>= assertBool ""
+
+test_acquire_timeout = TestCase $ testRedis2 $
+    let act v r = do acquire r "lock" 2000 50 >>= assertBool ""
+                     putMVar v "done"
+    in do v <- liftIO newEmptyMVar
+          r1 <- ask
+          r2 <- ask2
+          liftIO $ do acquire r1 "lock" 1000 50 >>= assertBool ""
+                      v2 <- later 500 $ do res <- tryTakeMVar v
+                                           release r1 "lock"
+                                           return res
+                      act v r2
+                      takeMVar v2 >>= assertEqual "" Nothing
diff --git a/Test/Monad/CASCommands.hs b/Test/Monad/CASCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/Monad/CASCommands.hs
@@ -0,0 +1,57 @@
+module Test.Monad.CASCommands where
+
+import Control.Monad.Reader
+import Control.Exception
+import Control.Concurrent (threadDelay)
+import Control.Concurrent.MVar
+import Test.HUnit
+import Database.Redis.Monad
+import Database.Redis.Monad.State
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "run_cas" test_run_cas,
+                  TestLabel "run_cas that raised an exception" test_run_cas_exception]
+
+run r = liftIO . runWithRedis r
+
+test_run_cas = TestCase $ testRedis2 $
+    let act :: RedisM String
+        act = do Just foo <- get "foo" >>= fromRBulk
+                 multi
+                 liftIO $ threadDelay 1000
+                 set "foo" "bar" >>= noError
+                 set "foo_" foo >>= noError
+                 exec :: RedisM (Reply ())
+                 return foo
+
+    in do r1 <- ask
+          r2 <- ask2
+          addStr
+          run r1 $ do foo <- run_cas ["foo", "bar"] act
+                      foo_ <- get "foo_" >>= fromRBulk :: RedisM (Maybe String)
+                      liftIO $ assertEqual "" (Just foo) foo_
+                      get "foo" >>= fromRBulk >>= liftIO . assertEqual "" (Just "bar")
+
+                      del "foo_"
+
+          liftIO $ later 500 $ runWithRedis r2 $ set "foo" "baz"
+
+          run r1 $ do run_cas ["foo", "bar"] act
+                      foo_ <- get "foo_" >>= fromRBulk :: RedisM (Maybe String)
+                      liftIO $ assertEqual "" Nothing foo_
+                      get "foo" >>= fromRBulk >>= liftIO . assertEqual "" (Just "baz")
+
+test_run_cas_exception = TestCase $ testRedis $
+    let act :: RedisM ()
+        act = do multi
+                 set "foo" "bar" >>= noError
+                 set "bar" "foo" >>= noError
+                 error "bang!"
+                 exec :: RedisM (Reply ())
+                 return ()
+    in do r <- ask
+          addStr
+          foo <- run r $ (get "foo" :: RedisM (Reply String))
+          liftIO $ assertRaises "" (ErrorCall undefined) $ runWithRedis r $ run_cas ["foo", "bar"] act
+          run r $ get "foo" >>= liftIO . assertEqual "" foo
diff --git a/Test/Monad/MultiCommands.hs b/Test/Monad/MultiCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/Monad/MultiCommands.hs
@@ -0,0 +1,60 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module Test.Monad.MultiCommands where
+
+import Control.Monad.Reader
+import Control.Exception
+import Control.Concurrent (threadDelay)
+import Control.Concurrent.MVar
+import Test.HUnit
+import Database.Redis.Monad
+import Database.Redis.Monad.State
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "run_multi" test_run_multi,
+                  TestLabel "run_multi with exception" test_run_multi_exception]
+
+run r = liftIO . runWithRedis r
+
+test_run_multi = TestCase $ testRedis2 $
+    let act = do set "foo" "cool" >>= liftIO . assertRQueued ""
+                 set "baz" "baz" >>= liftIO . assertRQueued ""
+                 liftIO $ threadDelay 2000
+
+    in do r1 <- ask
+          r2 <- ask2
+          addStr
+          (foo :: Maybe String) <- run r2 $ do foo <- get "foo" >>= fromRBulk
+                                               exists "baz" >>= fromRInt >>= liftIO . assertEqual "" 0
+                                               return foo
+
+          v <- liftIO $ later 1000 $ runWithRedis r2 $ get "foo" >>= liftIO . fromRBulk
+
+          run r1 $ (run_multi act :: RedisM (Reply ()))
+
+          liftIO $ takeMVar v >>= assertEqual "exec was not called yet" foo
+
+          run r2 $ do get "foo" >>= fromRBulk >>= liftIO . assertEqual "now foo got a new value" (Just "cool")
+                      exists "baz" >>= fromRInt >>= liftIO . assertEqual "now baz is exists" 1
+
+test_run_multi_exception = TestCase $ testRedis2 $
+    let act = do set "foo" "cool" >>= liftIO . assertRQueued ""
+                 set "baz" "baz" >>= liftIO . assertRQueued ""
+                 liftIO $ threadDelay 2000
+                 error "bang!"
+
+    in do r1 <- ask
+          r2 <- ask2
+          addStr
+          foo <- run r2 $ do foo <- get "foo" >>= fromRBulk :: RedisM (Maybe String)
+                             exists "baz" >>= fromRInt >>= liftIO . assertEqual "" 0
+                             return foo
+
+          v <- liftIO $ later 1000 $ runWithRedis r2 $ get "foo" >>= fromRBulk
+
+          liftIO $ assertRaises "" (ErrorCall undefined) $ runWithRedis r1 $ (run_multi act :: RedisM (Reply ())) >> return ()
+
+          liftIO $ takeMVar v >>= assertEqual "exec was not called yet" foo
+
+          run r2 $ do get "foo" >>= fromRBulk >>= liftIO . assertEqual "exception catched and multi statement was discarder" foo
+                      exists "baz" >>= fromRInt >>= liftIO . assertEqual "exception catched and multi statement was discarder" 0
diff --git a/Test/MultiCommands.hs b/Test/MultiCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/MultiCommands.hs
@@ -0,0 +1,94 @@
+module Test.MultiCommands where
+
+import Control.Monad.Reader
+import Control.Exception
+import Control.Concurrent (threadDelay)
+import Control.Concurrent.MVar
+import Test.HUnit
+import Database.Redis.Redis
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "multi and exec" test_multi_exec,
+                  TestLabel "multi and discard" test_multi_discard,
+                  TestLabel "run_multi" test_run_multi,
+                  TestLabel "run_multi with exception" test_run_multi_exception]
+
+test_multi_exec = TestCase $ testRedis2 $
+    do r1 <- ask
+       r2 <- ask2
+       addStr
+       liftIO $ do foo <- get r2 "foo" >>= fromRBulk :: IO (Maybe String)
+                   exists r2 "baz" >>= fromRInt >>= assertEqual "" 0
+
+                   multi r1
+                   set r1 "foo" "cool" >>= assertRQueued ""
+                   set r1 "baz" "baz" >>= assertRQueued ""
+
+                   get r2 "foo" >>= fromRBulk >>= assertEqual "exec was not called yet" foo
+                   exists r2 "baz" >>= fromRInt >>= assertEqual "exec was not called yet" 0
+
+                   (exec r1 :: IO (Reply String)) >>= fromRMulti >>= assertEqual "" (Just [ROk, ROk])
+
+                   get r2 "foo" >>= fromRBulk >>= assertEqual "now foo got a new value" (Just "cool")
+                   exists r2 "baz" >>= fromRInt >>= assertEqual "now baz is exists" 1
+
+test_multi_discard = TestCase $ testRedis2 $
+    do r1 <- ask
+       r2 <- ask2
+       addStr
+       liftIO $ do foo <- get r2 "foo" >>= fromRBulk :: IO (Maybe String)
+                   exists r2 "baz" >>= fromRInt >>= assertEqual "" 0
+
+                   multi r1
+                   set r1 "foo" "cool" >>= assertRQueued ""
+                   set r1 "baz" "baz" >>= assertRQueued ""
+
+                   get r2 "foo" >>= fromRBulk >>= assertEqual "exec was not called yet" foo
+                   exists r2 "baz" >>= fromRInt >>= assertEqual "exec was not called yet" 0
+
+                   discard r1 >>= assertEqual "" ROk
+
+                   get r2 "foo" >>= fromRBulk >>= assertEqual "statements was discarder" foo
+                   exists r2 "baz" >>= fromRInt >>= assertEqual "statements was discarded" 0
+
+test_run_multi = TestCase $ testRedis2 $
+    let act r = do (set r "foo" "cool" :: IO (Reply ())) >>= assertRQueued ""
+                   (set r "baz" "baz" :: IO (Reply ())) >>= assertRQueued ""
+                   threadDelay 2000
+
+    in do r1 <- ask
+          r2 <- ask2
+          addStr
+          liftIO $ do foo <- get r2 "foo" >>= fromRBulk :: IO (Maybe String)
+                      exists r2 "baz" >>= fromRInt >>= assertEqual "" 0
+
+                      v <- later 1000 $ get r2 "foo" >>= fromRBulk
+
+                      run_multi r1 act  :: IO (Reply ())
+
+                      takeMVar v >>= assertEqual "exec was not called yet" foo
+
+                      get r2 "foo" >>= fromRBulk >>= assertEqual "now foo got a new value" (Just "cool")
+                      exists r2 "baz" >>= fromRInt >>= assertEqual "now baz is exists" 1
+
+test_run_multi_exception = TestCase $ testRedis2 $
+    let act r = do (set r "foo" "cool" :: IO (Reply ())) >>= assertRQueued ""
+                   (set r "baz" "baz" :: IO (Reply ())) >>= assertRQueued ""
+                   threadDelay 2000
+                   error "bang!"
+
+    in do r1 <- ask
+          r2 <- ask2
+          addStr
+          liftIO $ do foo <- get r2 "foo" >>= fromRBulk :: IO (Maybe String)
+                      exists r2 "baz" >>= fromRInt >>= assertEqual "" 0
+
+                      v <- later 1000 $ get r2 "foo" >>= fromRBulk
+
+                      assertRaises "" (ErrorCall undefined) $ (run_multi r1 act  :: IO (Reply ())) >> return ()
+
+                      takeMVar v >>= assertEqual "exec was not called yet" foo
+
+                      get r2 "foo" >>= fromRBulk >>= assertEqual "exception catched and multi statement was discarder" foo
+                      exists r2 "baz" >>= fromRInt >>= assertEqual "exception catched and multi statement was discarder" 0
diff --git a/Test/PubSubCommands.hs b/Test/PubSubCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/PubSubCommands.hs
@@ -0,0 +1,83 @@
+module Test.PubSubCommands where
+
+import Control.Monad.Reader
+import Control.Exception
+import Control.Concurrent.MVar
+import Test.HUnit
+import Database.Redis.Redis
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "subscribe and unsubscribe" test_subscribe_unsubscribe,
+                  TestLabel "psubscribe and punsubscribe" test_psubscribe_punsubscribe,
+                  TestLabel "publish and listen" test_publish_listen,
+                  TestLabel "publish and listen for psubscribe" test_publish_listen_p]
+
+test_subscribe_unsubscribe = TestCase $ testRedis $
+    do r <- ask
+       liftIO $ do subscribed r >>= assertEqual "" 0
+                   res <- subscribe r ["foo", "bar"]
+                   assertEqual "" [MSubscribe "foo" 1, MSubscribe "bar" 2] res
+                   subscribed r >>= assertEqual "" 2
+
+                   res <- subscribe r ["foo", "baz"]
+                   assertEqual "" [MSubscribe "foo" 2, MSubscribe "baz" 3] res
+                   subscribed r >>= assertEqual "" 3
+
+                   res <- unsubscribe r ["foo"]
+                   assertEqual "" [MUnsubscribe "foo" 2] res
+                   subscribed r >>= assertEqual "" 2
+
+                   ping r >>= assertRError ""
+
+                   res <- unsubscribe r ([] :: [String]) :: IO [Message String]
+                   subscribed r >>= assertEqual "" 0
+
+test_psubscribe_punsubscribe = TestCase $ testRedis $
+    do r <- ask
+       liftIO $ do subscribed r >>= assertEqual "" 0
+                   res <- psubscribe r ["foo*", "bar*"]
+                   assertEqual "" [MPSubscribe "foo*" 1, MPSubscribe "bar*" 2] res
+                   subscribed r >>= assertEqual "" 2
+
+                   res <- psubscribe r ["foo*", "baz*"]
+                   assertEqual "" [MPSubscribe "foo*" 2, MPSubscribe "baz*" 3] res
+                   subscribed r >>= assertEqual "" 3
+
+                   res <- punsubscribe r ["foo*"]
+                   assertEqual "" [MPUnsubscribe "foo*" 2] res
+                   subscribed r >>= assertEqual "" 2
+
+                   ping r >>= assertRError ""
+
+                   res <- punsubscribe r ([] :: [String]) :: IO [Message String]
+                   subscribed r >>= assertEqual "" 0
+
+test_publish_listen = TestCase $ testRedis2 $
+    do r1 <- ask
+       r2 <- ask2
+       liftIO $ do subscribed r1 >>= assertEqual "" 0
+                   subscribe r1 ["foo"] :: IO [Message ()]
+                   v <- later 500 $ publish r2 "foo" "bar" >>= fromRInt
+                   msg <- listen r1 1000
+                   assertEqual "" (Just $ MMessage "foo" "bar") msg
+                   takeMVar v >>= assertEqual "" 1
+
+                   (listen r1 500 :: IO (Maybe (Message String))) >>= assertEqual "" Nothing
+
+test_publish_listen_p = TestCase $ testRedis2 $
+    do r1 <- ask
+       r2 <- ask2
+       liftIO $ do subscribed r1 >>= assertEqual "" 0
+                   psubscribe r1 ["foo*", "f*"] :: IO [Message ()]
+                   v <- later 500 $ publish r2 "fun" "funny" >>= fromRInt
+                   msg <- listen r1 1000
+                   assertEqual "" (Just $ MPMessage "f*" "fun" "funny") msg
+                   takeMVar v >>= assertEqual "" 1
+                   v <- later 500 $ publish r2 "foot" "football" >>= fromRInt
+                   msg <- listen r1 1000
+                   assertEqual "" (Just $ MPMessage "foo*" "foot" "football") msg
+                   msg <- listen r1 1000
+                   assertEqual "" (Just $ MPMessage "f*" "foot" "football") msg
+                   takeMVar v >>= assertEqual "" 2
+                   (listen r1 500 :: IO (Maybe (Message String))) >>= assertEqual "" Nothing
diff --git a/Test/SetCommands.hs b/Test/SetCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/SetCommands.hs
@@ -0,0 +1,123 @@
+module Test.SetCommands where
+
+import Test.HUnit
+import Control.Monad.Reader
+import Data.Maybe
+import Data.Set
+import Database.Redis.Redis
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "smembers" test_smembers,
+                  TestLabel "sismember" test_sismember,
+                  TestLabel "sadd" test_sadd,
+                  TestLabel "srem" test_srem,
+                  TestLabel "spop" test_spop,
+                  TestLabel "smove" test_smove,
+                  TestLabel "scard" test_scard,
+                  TestLabel "srandmember" test_srandmember,
+                  TestLabel "sinter and sinterStore" test_sinter_sinterstore,
+                  TestLabel "sunion and sunionStore" test_sunion_sunionstore,
+                  TestLabel "sdiff and sdiffStore" test_sdiff_sdiffstore]
+
+asSet :: Reply String -> IO (Set (Maybe String))
+asSet r = fromRMultiBulk r >>= return . fromList . fromJust
+        
+test_smembers = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do s <- smembers r "set" >>= asSet
+                   assertEqual "" (fromList [Just "1", Just "2", Just "3"]) s
+
+test_sismember = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do sismember r "set" "1" >>= fromRInt >>= assertEqual "" 1
+                   sismember r "set" "0" >>= fromRInt >>= assertEqual "" 0
+                   sismember r "no-such-key" "1" >>= fromRInt >>= assertEqual "" 0
+
+test_sadd = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do s <- smembers r "set" >>= asSet
+                   sadd r "set" "3" >>= fromRInt >>= assertEqual "element allready exists" 0
+                   sadd r "set" "4" >>= fromRInt >>= assertEqual "" 1
+                   s' <- smembers r "set" >>= asSet
+                   assertEqual "" (insert (Just "4") s) s'
+
+test_srem = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do s <- smembers r "set" >>= asSet
+                   srem r "set" "4" >>= fromRInt >>= assertEqual "element is not a member" 0
+                   srem r "set" "3" >>= fromRInt >>= assertEqual "" 1
+                   s' <- smembers r "set" >>= asSet
+                   assertEqual "" (delete (Just "3") s) s'
+
+test_spop = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do s <- smembers r "set" >>= asSet
+                   el <- spop r "set" >>= fromRBulk :: IO (Maybe String)
+                   assertBool "" $ member el s
+                   s' <- smembers r "set" >>= asSet
+                   assertEqual "" (delete el s) s'
+
+test_smove = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do s <- smembers r "set" >>= asSet
+                   smove r "set" "set2" "3" >>= fromRInt >>= assertEqual "" 1
+                   s' <- smembers r "set" >>= asSet
+                   assertEqual "" (delete (Just "3") s) s'
+                   s' <- smembers r "set2" >>= asSet
+                   assertEqual "" (fromList [Just "3"]) s'
+
+test_scard = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do s <- smembers r "set" >>= asSet
+                   scard r "set" >>= fromRInt >>= assertEqual "" (size s)
+
+test_srandmember = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do s <- smembers r "set" >>= asSet
+                   el <- srandmember r "set" >>= fromRBulk
+                   assertBool "" $ member el s
+
+test_sinter_sinterstore = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do mapM_ (sadd r "set2") ["2", "3", "4"]
+                   s <- smembers r "set" >>= asSet
+                   s2 <- smembers r "set2" >>= asSet
+                   s3 <- sinter r ["set", "set2"] >>= asSet
+                   assertEqual "" (intersection s s2) s3
+                   sinterStore r "set3" ["set", "set2"] >>= fromRInt >>= assertEqual "" (size s3)
+                   smembers r "set3" >>= asSet >>= assertEqual "" s3
+                   sinterStore r "set3" ["set", "set4"] >>= fromRInt >>= assertEqual "" 0
+
+test_sunion_sunionstore = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do mapM_ (sadd r "set2") ["3", "4", "5"]
+                   s <- smembers r "set" >>= asSet
+                   s2 <- smembers r "set2" >>= asSet
+                   s3 <- sunion r ["set", "set2"] >>= asSet
+                   assertEqual "" (union s s2) s3
+                   sunionStore r "set3" ["set", "set2"] >>= fromRInt >>= assertEqual "" (size s3)
+                   smembers r "set3" >>= asSet >>= assertEqual "" s3
+                   sunionStore r "set3" ["set", "set4"] >>= fromRInt >>= assertEqual "" (size s)
+
+test_sdiff_sdiffstore = TestCase $ testRedis $
+    do r <- ask
+       addSet
+       liftIO $ do mapM_ (sadd r "set2") ["3", "4", "5"]
+                   s <- smembers r "set" >>= asSet
+                   s2 <- smembers r "set2" >>= asSet
+                   s3 <- sdiff r ["set", "set2"] >>= asSet
+                   assertEqual "" (difference s s2) s3
+                   sdiffStore r "set3" ["set", "set2"] >>= fromRInt >>= assertEqual "" (size s3)
+                   smembers r "set3" >>= asSet >>= assertEqual "" s3
+                   sdiffStore r "set3" ["set", "set4"] >>= fromRInt >>= assertEqual "" (size s)
diff --git a/Test/Setup.hs b/Test/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Test/Setup.hs
@@ -0,0 +1,69 @@
+{-# LANGUAGE FlexibleContexts #-}
+module Test.Setup where
+
+import Control.Exception (bracket)
+import Control.Concurrent
+import Control.Monad.Reader
+import Test.HUnit
+import System.Cmd
+import System.Directory
+import System.FilePath
+import Database.Redis.Redis
+
+startRedis :: FilePath -> IO ()
+startRedis path_to_redis = do cwd <- getCurrentDirectory
+                              system $ path_to_redis ++ " " ++ (cwd </> "redis.conf")
+                              threadDelay $ 10000
+                              return ()
+
+shutdownRedis = do r <- connect localhost defaultPort
+                   shutdown r
+                   return ()
+
+testRedis :: (ReaderT Redis IO ()) -> IO ()
+testRedis t = bracket setup teardown $ runReaderT t
+    where setup = do r <- connect localhost defaultPort
+                     select r 0
+                     return r
+          teardown r = do flushAll r
+                          disconnect r
+
+testRedis2 :: (ReaderT Redis (ReaderT Redis IO) ()) -> IO ()
+testRedis2 t = bracket setup teardown $ \(r1, r2) -> runReaderT (runReaderT t r2) r1
+    where setup = do r1 <- connect localhost defaultPort
+                     select r1 0
+                     r2 <- connect localhost defaultPort
+                     select r2 0
+                     return (r1, r2)
+          teardown (r1, r2) = do flushAll r1
+                                 disconnect r1
+                                 disconnect r2
+
+ask2 :: (MonadReader a m, MonadTrans t) => t m a
+ask2 = lift ask
+
+addStr :: (MonadReader Redis m, MonadIO m) => m ()
+addStr = do r <- ask
+            liftIO $ do set r "foo" "foo"
+                        set r "bar" "bar"
+            return ()
+
+addList :: (MonadReader Redis m, MonadIO m) => m ()
+addList = do r <- ask
+             liftIO $ mapM_ (rpush r "list") ["1", "2", "3"]
+
+
+addSet :: (MonadReader Redis m, MonadIO m) => m ()
+addSet = do r <- ask
+            liftIO $ mapM_ (sadd r "set") ["1", "2", "3"]
+
+addZSet :: (MonadReader Redis m, MonadIO m) => m ()
+addZSet = do r <- ask
+             liftIO $ mapM_ (uncurry (zadd r "zset")) $ zip (reverse [1.0, 2.0, 3.0, 4.0, 5.0]) ["1", "2", "3", "4", "5"]
+
+addHash :: (MonadReader Redis m, MonadIO m) => m ()
+addHash = do r <- ask
+             liftIO $ mapM_ (uncurry (hset r "hash")) $ zip ["foo", "bar", "baz"] ["1", "2", "3"]
+
+addAll :: (MonadReader Redis m, MonadIO m) => m ()
+addAll = addStr >> addList >> addSet >> addZSet >> addHash
diff --git a/Test/SortCommands.hs b/Test/SortCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/SortCommands.hs
@@ -0,0 +1,90 @@
+module Test.SortCommands where
+
+import Test.HUnit
+import Control.Monad.Reader
+import Data.Maybe
+import Data.Char (toUpper)
+import Data.List hiding (sort)
+import Data.ByteString.UTF8 (fromString)
+import qualified Data.List as L
+import System.Random
+import Database.Redis.Redis
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "sort asc and desc" test_sort_asc_desc,
+                  TestLabel "sort alpha" test_sort_alpha,
+                  TestLabel "sort_by" test_sort_by,
+                  TestLabel "sort get_obj" test_sort_get,
+                  TestLabel "sort store" test_sort_store,
+                  TestLabel "listRelated" test_listRelated]
+
+addUserList name l = do r <- ask
+                        lift $ mapM_ (rpush r name) l
+
+unsortedList = randomRIOs ((0 :: Int), 99) 10
+unsortedAlphaList = randomRIOs ('a', 'z') 10
+unsortedAlphaList2 = randomRIOs ('а', 'я') 10 -- non-ASCII string
+
+test_sort_asc_desc = TestCase $ testRedis $
+    do r <- ask
+       l <- liftIO unsortedList
+       addUserList "l" l
+       liftIO $ do l' <- sort r "l" sortDefaults >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" (L.sort l) l'
+                   l' <- sort r "l" sortDefaults{desc = True} >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" (reverse $ L.sort l) l'
+
+test_sort_alpha = TestCase $ testRedis $
+    do r <- ask
+       l1 <- liftIO unsortedAlphaList
+       l2 <- liftIO unsortedAlphaList2
+       addUserList "l1" l1
+       addUserList "l2" l2
+       liftIO $ do l' <- sort r "l1" sortDefaults{alpha = True} >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" (L.sort l1) l'
+                   l' <- sort r "l2" sortDefaults{alpha = True} >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" (L.sort l2) l'
+                   l' <- sort r "l1" sortDefaults{desc = True, alpha = True} >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" (reverse $ L.sort l1) l'
+                   l' <- sort r "l2" sortDefaults{desc = True, alpha = True} >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" (reverse $ L.sort l2) l'
+
+test_sort_by = TestCase $ testRedis $
+    do r <- ask
+       l <- liftIO unsortedList
+       addUserList "l" l
+       -- set bounch of kes foo_* for sort l in reverse order
+       liftIO $ mapM_ (\x -> set r ("foo_" ++ show x) (-x)) [(0 :: Int)..99]
+
+       liftIO $ do l' <- sort r "l" sortDefaults{sort_by = fromString "foo_*"} >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" (reverse $ L.sort l) l'
+                   l' <- sort r "l" sortDefaults{sort_by = fromString "constant"} >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" l l'
+
+test_sort_get = TestCase $ testRedis $
+    do r <- ask
+       l <- liftIO unsortedAlphaList
+       addUserList "l" l
+       liftIO $ mapM_ (\x -> set r ("foo_" ++ [x]) (toUpper x) >> set r ("bar_" ++ [x]) 'x') ['a' .. 'z']
+
+       liftIO $ do l' <- sort r "l" sortDefaults{alpha = True, get_obj = [fromString "foo_*", fromString "bar_*"]} >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" ((++ "x") $ intersperse 'x' $ map toUpper $ L.sort l) l'
+
+test_sort_store = TestCase $ testRedis $
+    do r <- ask
+       l <- liftIO unsortedAlphaList
+       addUserList "l" l
+       liftIO $ mapM_ (\x -> set r ("foo_" ++ [x]) (toUpper x) >> set r ("bar_" ++ [x]) 'x') ['a' .. 'z']
+       liftIO $ do l' <- sort r "l" sortDefaults{alpha = True, get_obj = [fromString "foo_*"]} >>= fromRMultiBulk >>= return . map fromJust . fromJust :: IO [Char]
+                   (sort r "l" sortDefaults{alpha = True, get_obj = [fromString "foo_*"], store = fromString "l2"} :: IO (Reply ())) >>= noError
+                   l'' <- lrange r "l2" takeAll >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual "" l' l''
+
+test_listRelated = TestCase $ testRedis $
+    do r <- ask
+       addList
+       liftIO $ mapM_ (\x -> set r ("foo_" ++ show x) (x^2)) [(1 :: Int)..3]
+       liftIO $ do l <- lrange r "list" takeAll >>= fromRMultiBulk >>= return . map fromJust . fromJust :: IO [Int]
+                   l' <- listRelated r "foo_*" "list" takeAll >>= fromRMultiBulk >>= return . map fromJust . fromJust
+                   assertEqual  "" (map (^2) l) l'
diff --git a/Test/StringCommands.hs b/Test/StringCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/StringCommands.hs
@@ -0,0 +1,92 @@
+module Test.StringCommands where
+
+import Test.HUnit
+import Control.Monad.Reader
+import Data.Maybe
+import Database.Redis.Redis
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "get and set" test_set_get,
+                  TestLabel "setNx" test_setNx,
+                  TestLabel "setEx" test_setEx,
+                  TestLabel "mSet and mGet" test_m_set_get,
+                  TestLabel "mSetNx" test_mSetNx,
+                  TestLabel "getSet" test_getSet,
+                  TestLabel "incr and incrBy, decr and decrBy" test_incr_decr,
+                  TestLabel "append" test_append,
+                  TestLabel "substr" test_substr,
+                  TestLabel "strlen" test_strlen]
+
+test_set_get = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do get r "foo" >>= fromRBulk >>= assertEqual "" (Just "foo")
+                   set r "foo" "zoo" >>= fromROk
+                   get r "foo" >>= fromRBulk >>= assertEqual "foo was set to zoo" (Just "zoo")
+
+test_setNx = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do setNx r "foo" "zoo" >>= fromRInt >>= assertEqual "setNx doesn't replace key value" 0
+                   get r "foo" >>= fromRBulk >>= assertEqual "" (Just "foo")
+
+test_setEx = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do setEx r "foo" 30 "zoo" >>= fromROk
+                   get r "foo" >>= fromRBulk >>= assertEqual "foo was set to zoo" (Just "zoo")
+                   ttl r "foo" >>= fromRInt >>= assertBool "foo TTL must be less then 30 seconds" . (<= 30)
+
+test_m_set_get = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do mSet r [("foo", "zoo"), ("zoo", "foo")] >>= fromROk
+                   mGet r ["foo", "zoo", "baz"] >>= fromRMultiBulk >>= assertEqual "" (Just [Just "zoo", Just "foo", Nothing])
+
+test_mSetNx = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do mSetNx r [("foo", "zoo"), ("zoo", "foo")] >>= fromRInt >>= assertEqual "foo already exists" 0
+                   mGet r ["foo", "zoo"] >>= fromRMultiBulk >>= assertEqual "" (Just [Just "foo", Nothing])
+
+test_getSet = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do getSet r "foo" "zoo" >>= fromRBulk >>= assertEqual "" (Just "foo")
+                   get r "foo" >>= fromRBulk >>= assertEqual "foo was set to zoo" (Just "zoo")
+
+test_incr_decr = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do set r "i" (0 :: Int) >>= fromROk
+                   incr r "i" >>= fromRInt >>= assertEqual "" 1
+                   (get r "i" :: IO (Reply String)) >>= fromRBulk >>= assertEqual "" (Just "1")
+                   incrBy r "i" 2 >>= fromRInt >>= assertEqual "" 3
+                   (get r "i" :: IO (Reply String)) >>= fromRBulk >>= assertEqual "" (Just "3")
+                   decr r "i" >>= fromRInt >>= assertEqual "" 2
+                   (get r "i" :: IO (Reply String)) >>= fromRBulk >>= assertEqual "" (Just "2")
+                   decrBy r "i" 2 >>= fromRInt >>= assertEqual "" 0
+                   (get r "i" :: IO (Reply String)) >>= fromRBulk >>= assertEqual "" (Just "0")
+
+test_append = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do Just foo <- get r "foo" >>= fromRBulk
+                   newlength <- append r "foo" "foo" >>= fromRInt
+                   Just foo' <- get r "foo" >>= fromRBulk
+                   assertEqual ("Expected: \"" ++ foo ++ "\" ++ \"foo\"") (foo ++ "foo") foo'
+                   assertEqual "" (length foo') newlength
+
+test_substr = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do Just foo <- get r "foo" >>= fromRBulk
+                   let s = take 1 . drop 1 $ foo :: String
+                   substr r "foo" (1, 1) >>= fromRBulk >>= assertEqual "" (Just s)
+
+test_strlen = TestCase $ testRedis $
+    do r <- ask
+       addStr
+       liftIO $ do Just foo <- get r "foo" >>= fromRBulk
+                   strlen r "foo" >>= fromRInt >>= assertEqual ("lenght of \"" ++ foo ++ "\"" ) (length foo)
diff --git a/Test/Utils.hs b/Test/Utils.hs
new file mode 100644
--- /dev/null
+++ b/Test/Utils.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE CPP #-}
+module Test.Utils where
+
+import System.Random
+import Control.Monad
+import Test.HUnit
+import Control.Exception
+import Control.Concurrent
+import Control.Concurrent.MVar
+import Database.Redis.Redis
+import Database.Redis.ByteStringClass
+
+#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 610
+assertRaises :: (Show a, Control.Exception.Exception e, Show e) =>
+                String -> e -> IO a -> IO ()
+#else
+assertRaises :: Show a => String -> Control.Exception.Exception -> IO a -> IO ()
+#endif
+assertRaises msg selector action =
+    let {- thetest e = if e == selector then return ()
+                    else assertFailure $ msg ++ "\nReceived unexpected exception: "
+                             ++ (show e) ++ "\ninstead of exception: " ++ (show selector)
+                             -}
+        thetest :: a -> a -> IO ()
+        thetest _ e = return ()
+        in do r <- Control.Exception.try action
+              case r of
+                Left e -> thetest selector e
+                Right _ -> assertFailure $ msg ++ "\nReceived no exception, but was expecting exception: " ++ (show selector)
+
+assertOneOf :: (Eq a, Show a) => String -> [a] -> a -> Assertion
+assertOneOf msg lst a = find lst
+    where find [] = assertFailure (msg ++ "\n" ++ show a ++ " not found in " ++ show lst)
+          find (x:xs) = if a == x
+                        then return ()
+                        else find xs
+
+assertRError :: BS a => String -> Reply a -> Assertion
+assertRError msg (RError _) = return ()
+assertRError msg r = assertFailure $ msg ++ "\n" ++ show r ++ " is not a RError"
+
+assertRQueued :: BS a => String -> Reply a -> Assertion
+assertRQueued msg RQueued = return ()
+assertRQueued msg r = assertFailure $ msg ++ "\n" ++ show r ++ " is not a RQueued"
+
+{------------------ Concurrent ------------------}
+later :: Int -> IO a -> IO (MVar a)
+later t a = do v <- newEmptyMVar
+               forkIO $ threadDelay t >> a >>= putMVar v
+               return v
+
+{-------------------- Random --------------------}
+(.:.) = liftM2 (:)
+infixr 5 .:.
+
+randomRIOs _ 0 = return []
+randomRIOs range n = randomRIO range .:. randomRIOs range (n-1)
diff --git a/Test/ZSetCommands.hs b/Test/ZSetCommands.hs
new file mode 100644
--- /dev/null
+++ b/Test/ZSetCommands.hs
@@ -0,0 +1,195 @@
+module Test.ZSetCommands where
+
+import Test.HUnit
+import Control.Monad.Reader
+import Data.Maybe
+import Data.List
+import Data.Map (Map(..), fromList, toList, unionWith, intersectionWith)
+import qualified Data.Map as M
+import Database.Redis.Redis
+import Test.Setup
+import Test.Utils
+
+tests = TestList [TestLabel "zrange, zrevrange and scard" test_zrange_zrevrange_zcard,
+                  TestLabel "zadd and zrem" test_zadd_zrem,
+                  TestLabel "zscore" test_zscore,
+                  TestLabel "zincrBy" test_zincrBy,
+                  TestLabel "zrangebyscore" test_zrangebyscore,
+                  TestLabel "zrevrangebyscore" test_zrevrangebyscore,
+                  TestLabel "zcount" test_zcount,
+                  TestLabel "zremrangebyscore" test_zremrangebyscore,
+                  TestLabel "zrank and zrevrank" test_zrank_zrevrank,
+                  TestLabel "zremrangebyrank" test_zremrangebyrank,
+                  TestLabel "zunionStore" test_zunionStore,
+                  TestLabel "zinterStore" test_zinterStore]
+
+asZSet :: Reply String -> IO [(String, Double)]
+asZSet r = fromRMultiBulk r >>= return . build . map fromJust . fromJust
+    where build (a:b:z) = (a, read b) : build z
+          build (a:[]) = error "unpaired element"
+          build [] = []
+
+zsort :: [(String, Double)] -> [(String, Double)]
+zsort = sortBy (\(_, a) (_, b) -> compare a b)
+
+test_zrange_zrevrange_zcard = TestCase $ testRedis $
+    let expected = zsort $ zip ["1", "2", "3", "4", "5"] (reverse [1.0, 2.0, 3.0, 4.0, 5.0])
+    in do r <- ask
+          addZSet
+          liftIO $ do zrange r "zset" takeAll True >>= asZSet >>= assertEqual "" expected
+                      zrevrange r "zset" takeAll True >>= asZSet >>= assertEqual "" (reverse expected)
+                      zcard r "zset" >>= fromRInt >>= assertEqual "" (length expected)
+                      zcard r "no-such-key" >>= fromRInt >>= assertEqual "" 0
+                      zrange r "no-such-key" takeAll True >>= asZSet >>= assertEqual "" []
+                      zrevrange r "no-such-key" takeAll True >>= asZSet >>= assertEqual "" []
+                      zrange r "zset" (1,2) True >>= asZSet >>= assertEqual "" (take 2 $ drop 1 $ expected)
+                      zrevrange r "zset" (1,2) True >>= asZSet >>= assertEqual "" (take 2 $ drop 1 $ reverse expected)
+                      zrange r "zset" (1,2) False >>= fromRMultiBulk >>= return . map fromJust . fromJust >>= assertEqual "" (map fst $ take 2 $ drop 1 $ expected)
+                      zrevrange r "zset" (1,2) False >>= fromRMultiBulk >>= return . map fromJust . fromJust >>= assertEqual "" (map fst $ take 2 $ drop 1 $ reverse expected)
+
+test_zadd_zrem = TestCase $ testRedis $
+    do r <- ask
+       addZSet
+       liftIO $ do zadd r "zset" 0.5 "6" >>= fromRInt >>= assertEqual "" 1
+                   zadd r "zset" 0.6 "1" >>= fromRInt >>= assertEqual "element is allready in set" 0
+                   z <- zrange r "zset" takeAll True >>= asZSet
+                   assertEqual "" ("6", 0.5) $ head z
+                   assertEqual "" ("1", 0.6) $ head $ tail z
+                   zrem r "zset" "6" >>= fromRInt >>= assertEqual "" 1
+                   zrem r "zset" "6" >>= fromRInt >>= assertEqual "" 0
+                   z <- zrange r "zset" takeAll True >>= asZSet
+                   assertEqual "" ("1", 0.6) $ head z
+
+test_zscore = TestCase $ testRedis $
+    let lookupBy _ _ [] = Nothing
+        lookupBy f a (l:ls) = if f l == a
+                              then Just l
+                              else lookupBy f a ls
+    in do r <- ask
+          addZSet
+          liftIO $ do z <- zrange r "zset" takeAll True >>= asZSet
+                      let expected = snd `fmap` lookupBy fst "1" z
+                      score <- zscore r "zset" "1" >>= fromRBulk :: IO (Maybe Double)
+                      assertEqual "" expected score
+
+test_zincrBy = TestCase $ testRedis $
+    do r <- ask
+       addZSet
+       liftIO $ do zincrBy r "zset" 0.5 "5" >>= fromRBulk >>= assertEqual "" (Just (1.5 :: Double))
+                   score <- zscore r "zset" "5"  >>= fromRBulk
+                   assertEqual "" (Just (1.5 :: Double)) score
+                   zincrBy r "zset" (-0.5) "5" >>= fromRBulk >>= assertEqual "" (Just (1.0 :: Double))
+                   score <- zscore r "zset" "5"  >>= fromRBulk
+                   assertEqual "" (Just (1.0 :: Double)) score
+
+test_zrangebyscore = TestCase $ testRedis $
+    do r <- ask
+       addZSet
+       liftIO $ do z <- zrange r "zset" takeAll True >>= asZSet
+                   z' <- zrangebyscore r "zset" [1, 4] (Just (1, 2)) True >>= asZSet
+                   let expected = take 2 $ drop 1 $ filter ((\x -> x >= 1 && x <= 4) . snd) z
+                   assertEqual "" expected z'
+                   z' <- zrangebyscore r "zset" (1 :: Double, 4 :: Double) (Just (1, 2)) True >>= asZSet
+                   let expected = take 2 $ drop 1 $ filter ((\x -> x > 1 && x < 4) . snd) z
+                   assertEqual "" expected z'
+
+test_zrevrangebyscore = TestCase $ testRedis $
+    do r <- ask
+       addZSet
+       liftIO $ do z <- zrange r "zset" takeAll True >>= asZSet
+                   z' <- zrevrangebyscore r "zset" [4, 1] (Just (1, 2)) True >>= asZSet
+                   let expected = take 2 $ drop 1 $ reverse $ filter ((\x -> x >= 1 && x <= 4) . snd) z
+                   assertEqual "[1, 4]" expected z'
+                   z' <- zrevrangebyscore r "zset" (4 :: Double, 1 :: Double) (Just (1, 2)) True >>= asZSet
+                   let expected = take 2 $ drop 1 $ reverse $ filter ((\x -> x > 1 && x < 4) . snd) z
+                   assertEqual "(1, 4)" expected z'
+
+test_zcount = TestCase $ testRedis $
+    do r <- ask
+       addZSet
+       liftIO $ do z <- zrange r "zset" takeAll True >>= asZSet
+                   z' <- zcount r "zset" [1, 4] >>= fromRInt
+                   let expected = filter ((\x -> x >= 1 && x <= 4) . snd) z
+                   assertEqual "" (length expected) z'
+                   z' <- zcount r "zset" (1 :: Double, 4 :: Double) >>= fromRInt
+                   let expected = filter ((\x -> x > 1 && x < 4) . snd) z
+                   assertEqual "" (length expected) z'
+
+test_zremrangebyscore = TestCase $ testRedis $
+    do r <- ask
+       addZSet
+       liftIO $ do z <- zrange r "zset" takeAll True >>= asZSet
+                   let removed = filter ((\x -> x >= 1 && x <= 4) . snd) z
+                       left = filter (not . (\x -> x >= 1 && x <= 4) . snd) z
+                   zremrangebyscore r "zset" (1, 4) >>= fromRInt >>= assertEqual "" (length removed)
+                   z' <- zrange r "zset" takeAll True >>= asZSet
+                   assertEqual "" left z'
+
+test_zrank_zrevrank = TestCase $ testRedis $
+    let rank e = findIndex (== e)
+    in do r <- ask
+          addZSet
+          liftIO $ do z <- zrange r "zset" takeAll False >>= fromRMultiBulk >>= return . fromJust
+                      c <- zrank r "zset" "3" >>= fromRInt
+                      assertEqual "" (fromJust $ rank (Just "3") z) c
+                      c <- zrevrank r "zset" "3" >>= fromRInt
+                      assertEqual "" (fromJust $ rank (Just "3") $ reverse z) c
+
+                      {- The next one doesn't works because of zrank returning
+                         "RBulk Nil" if requested element is not a
+                         member of set
+                         zrank r "zset" "6" >>= fromRInt
+                       -}
+
+test_zremrangebyrank = TestCase $ testRedis $
+    do r <- ask
+       addZSet
+       liftIO $ do z <- zrange r "zset" takeAll False >>= fromRMultiBulk >>= return . fromJust :: IO [Maybe String]
+                   let expected = (take 1 z) ++ (drop 4 $ z) -- cut elements from 1 to 3
+                   zremrangebyrank r "zset" (1, 3) >>= fromRInt >>= assertEqual "" (length z - length expected)
+                   z' <- zrange r "zset" takeAll False >>= fromRMultiBulk >>= return . fromJust :: IO [Maybe String]
+                   assertEqual "" expected z'
+
+test_zunionStore = TestCase $ testRedis $
+    let addZSet2 key m = ask >>= \r ->  liftIO $ mapM_ (uncurry (flip $ zadd r key)) $ toList m
+        zmap2 = fromList $ zip ["1", "2", "6"] [1.0, 2.0, 3.0] :: Map String Double
+    in do r <- ask
+          addZSet
+          addZSet2 "zset2" zmap2
+          liftIO $ do z <- zrange r "zset" takeAll True >>= asZSet
+                      let zmap = fromList z :: Map String Double
+                          expected = zsort $ toList $ unionWith (+) zmap zmap2
+                      res <- zunionStore r "zset3" ["zset", "zset2"] [1, 1] SUM >>= fromRInt
+                      assertEqual "" (length expected) res
+                      z' <- zrange r "zset3" takeAll True >>= asZSet
+                      assertEqual "" expected z'
+
+                      let zmap = fromList z :: Map String Double
+                          -- in Redis weights is applied to maps before aggregation
+                          expected = zsort $ toList $ unionWith (+) zmap $ M.map (* 0.5) zmap2
+                      res <- zunionStore r "zset3" ["zset", "zset2"] [1, 0.5] SUM >>= fromRInt
+                      assertEqual "" (length expected) res
+                      z' <- zrange r "zset3" takeAll True >>= asZSet
+                      assertEqual "" expected z'
+
+test_zinterStore = TestCase $ testRedis $
+    let addZSet2 key m = ask >>= \r -> liftIO $ mapM_ (uncurry (flip $ zadd r key)) $ toList m
+        zmap2 = fromList $ zip ["1", "2", "6"] [1.0, 2.0, 3.0] :: Map String Double
+    in do r <- ask
+          addZSet
+          addZSet2 "zset2" zmap2
+          liftIO $ do z <- zrange r "zset" takeAll True >>= asZSet
+                      let zmap = fromList z :: Map String Double
+                          expected = zsort $ toList $ intersectionWith (+) zmap zmap2
+                      res <- zinterStore r "zset3" ["zset", "zset2"] [1, 1] SUM >>= fromRInt
+                      assertEqual "" (length expected) res
+                      z' <- zrange r "zset3" takeAll True >>= asZSet
+                      assertEqual "" expected z'
+
+                      let zmap = fromList z :: Map String Double
+                          -- in Redis weights is applied to maps before aggregation
+                          expected = zsort $ toList $ intersectionWith (+) zmap $ M.map (* 0.5) zmap2
+                      res <- zinterStore r "zset3" ["zset", "zset2"] [1, 0.5] SUM >>= fromRInt
+                      assertEqual "" (length expected) res
+                      z' <- zrange r "zset3" takeAll True >>= asZSet
+                      assertEqual "" expected z'
diff --git a/redis.cabal b/redis.cabal
--- a/redis.cabal
+++ b/redis.cabal
@@ -1,5 +1,5 @@
 Name:                redis
-Version:             0.9
+Version:             0.10
 License:             MIT
 Maintainer:          Alexander Bogdanov <andorn@gmail.com>
 Author:              Alexander Bogdanov <andorn@gmail.com>
@@ -7,28 +7,66 @@
 Category:            Database
 Synopsis:            A driver for Redis key-value database
 Description:
-    Redis is an advanced key-value store. It is similar to memcached
-    but the dataset is not volatile. Values can be strings, exactly
-    like in memcached, but also lists, sets, and ordered sets.
+	Redis (<http://redis.io>) is an open source, BSD licensed, advanced
+	key-value store. It is often referred as a data structure server
+	since keys can contain different data structures, such as strings,
+	hashes, lists, sets and sorted sets.
     .
     This library is a Haskell driver for Redis. Note that this library
-	supports the most recent (actually the git one) version of Redis
-	protocol. Most of the functions will work correctly with stable version
-	but not all.
+	supports the most recent (actually the git one) version of
+	Redis. Most of the functions will work correctly with stable
+	version but not all.
 	.
-	Changes from v0.8:
+	Changes from v0.9:
 	.
-	- Now it's able to connect Redis using unix sockets. To do so just
-	call /connect/ with path to socket as first argument and empty
-	second argument.
+	- New commands implemented: echo, linsert, zrevrangebyscore,
+      lpushx and rpushx
 	.
+	- blpop and brpop has changed their types: it's now IO (Maybe (s1,
+      s2)) instead of IO (Reply s2). Warning! It's backward
+      incompatible!
+	.
+	- New helpers fromRBulk' and fromRMultiBulk' which not only
+      unwraps RBulk and RMulti replies but also (unsafely) unwraps
+      /Maybes/ inside it.
+	.
+	- Now it's posible to use Redis renamed commands (config option
+      /rename-command/). You just have to call /renameCommand/ to make
+      client configuration the same as the server one.
+	.
+	- Type of run_multi changed. The second param is now (Redis -> IO
+      a) action instead of list of IO (Reply ()). Warning! It's
+      backward incompatible!
+	.
+	- Type of run_cas changed too. The third param is now (Redis -> IO
+      a) action instead of IO (Reply ()). Warning! It's backward
+      incompatible!
+	.
+	- Most of the protocol functions is now covered with tests (and
+      all tests are passed with the most recent Redis version). You
+      may run it using something like \"runhaskell Test.hs
+      \<path-to-your-redis-binary\>\". \*Warning!\* Don't do that if you
+      have running redis instance on the default port and host! All
+      data in databases 0 and 1 will be lost!
+	.
 
 Stability:           beta
 Build-Type:          Simple
 Cabal-Version: >= 1.4
 
+Extra-Source-Files: Test.hs,
+					Test/CASCommands.hs, Test/ListCommands.hs,
+					Test/PubSubCommands.hs, Test/StringCommands.hs,
+					Test/Connection.hs, Test/Lock.hs,
+					Test/SetCommands.hs, Test/Utils.hs,
+					Test/GenericCommands.hs, Test/Setup.hs,
+					Test/ZSetCommands.hs, Test/HashCommands.hs,
+					Test/MultiCommands.hs, Test/SortCommands.hs,
+					Test/Monad/CASCommands.hs, Test/Monad/MultiCommands.hs,
+					redis.conf
+
 Library
-    Build-Depends:       base < 5, bytestring, utf8-string,
+    Build-Depends:       base < 5, containers, bytestring, utf8-string,
                          network, mtl, old-time, MonadCatchIO-mtl
     Exposed-modules:     Database.Redis.Redis
                          Database.Redis.Monad
diff --git a/redis.conf b/redis.conf
new file mode 100644
--- /dev/null
+++ b/redis.conf
@@ -0,0 +1,23 @@
+daemonize yes
+unixsocket /tmp/redis.sock
+timeout 300
+loglevel warning
+logfile stdout
+databases 2
+
+# No snapshots
+# save 900 1
+# save 300 10
+# save 60 10000
+
+dir ./
+
+# requirepass foobared
+appendonly no
+vm-enabled no
+glueoutputbuf yes
+hash-max-zipmap-entries 64
+hash-max-zipmap-value 512
+activerehashing yes
+
+rename-command PING "P"
