packages feed

memcached-binary-0.1.0: test/test.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE LambdaCase #-}

module Main (main) where

import Network
import System.Process

import Control.Exception
import Control.Concurrent
import Control.Monad

import Data.Default.Class
import Data.Maybe
import Data.Word
import Data.Typeable
import Data.Version
import qualified Data.ByteString.Char8 as S
import qualified Data.ByteString.Lazy.Char8 as L

import Database.Memcached.Binary.Types.Exception
import Database.Memcached.Binary.IO (Connection, withConnection)
import qualified Database.Memcached.Binary.IO    as McIO
import qualified Database.Memcached.Binary.Maybe as McMaybe

import Test.HUnit hiding (Test)
import Test.Framework
import Test.Framework.Providers.HUnit

startMemcached :: IO ProcessHandle
startMemcached = do
    h <- spawnProcess "memcached" []
    wait (100 :: Int)
    return h
  where
    wait 0 = fail "cannot start server"
    wait i = handle (\(_ ::SomeException) -> threadDelay 100000 >> wait (i-1)) $
        void $ connectTo "localhost" $ PortNumber 11211

precond :: Connection -> IO ()
precond c = do
    McIO.flushAll c
    void $ McIO.set 0 0 "foo" "foovalue"   c
    void $ McIO.set 1 0 "bar" "1234567890" c

testMc :: TestName -> (Connection -> IO ()) -> Test
testMc title m = testCase title $ withConnection def (\c -> precond c >> m c)

newtype ByPassException = ByPassException String deriving (Typeable)
instance Show ByPassException where
    show (ByPassException s) = s

instance Exception ByPassException

assertException :: Word16 -> S.ByteString -> IO a -> IO ()
assertException ex msg m =
    (m >> throwIO (ByPassException "exception not occured.")) `catch`
    (\e -> case fromException e :: Maybe MemcachedException of
        Nothing -> assertFn e
        Just e'@(MemcachedException i _) -> unless (i == ex) (assertFn e'))
  where
    assertFn e = assertFailure $ unlines
        [ "not expected exception occured:"
        , "expected: " ++ show (MemcachedException ex msg)
        , "occured:  " ++ show e
        ]

testGet :: Test
testGet = testGroup "get"
    [ testGroup "IO module"
        [ testGroup "get"
            [ doTest "foo" (0, "foovalue")   McIO.get
            , doTest "bar" (1, "1234567890") McIO.get
            , doTestException "notexist"     McIO.get
            ]
        , testGroup "get_"
            [ doTest "foo" "foovalue"    McIO.get_
            , doTest "bar" "1234567890"  McIO.get_
            , doTestException "notexist" McIO.get_
            ]
        ]
    , testGroup "Maybe module"
        [ testGroup "get"
            [ doTest "foo"      (Just (0, "foovalue"))   McMaybe.get
            , doTest "bar"      (Just (1, "1234567890")) McMaybe.get
            , doTest "notexist" Nothing                  McMaybe.get
            ]
         , testGroup "get_"
            [ doTest "foo"      (Just "foovalue")   McMaybe.get_
            , doTest "bar"      (Just "1234567890") McMaybe.get_
            , doTest "notexist" Nothing             McMaybe.get_
            ]
        ]
    ]
  where
    doTest key ex fn = testMc (S.unpack key) $ \c -> do
        v <- fn key c
        v @?= ex
    doTestException key fn = testMc (S.unpack key) $ \c ->
        assertException 1 "Not found" $ fn key c

testSetAddReplace :: Test
testSetAddReplace = testGroup "set/add/replace"
    [ testGroup "set"
        [ testMc "set foo to foomod" $ \c -> do
            McIO.set 100 0 "foo" "foomod" c
            v <- McIO.get "foo" c
            v @?= (100, "foomod")
        , testMc "set notexist to exist" $ \c -> do
            McIO.set 100 0 "notexist" "exist" c
            v <- McIO.get "notexist" c
            v @?= (100, "exist")
        ]
    , testGroup "add"
        [ testMc "add foo to foomod" $ \c ->
            assertException 2 "Data exists for key." $
                McIO.add 100 0 "foo" "foomod" c
        , testMc "add notexist to exist" $ \c -> do
            McIO.add 100 0 "notexist" "exist" c
            v <- McIO.get "notexist" c
            v @?= (100, "exist")
        ]
    , testGroup "replace"
        [ testMc "set foo to foomod" $ \c -> do
            McIO.replace 100 0 "foo" "foomod" c
            v <- McIO.get "foo" c
            v @?= (100, "foomod")
        , testMc "set notexist to exist" $ \c ->
            assertException 1 "Not found" $
                McIO.replace 100 0 "notexist" "exist" c
        ]
    ]

testDelete :: Test
testDelete = testGroup "delete"
    [ testGroup "IO module"
        [ testMc "foo" $ \c -> do
            McIO.delete "foo" c
            r <- McMaybe.get "foo" c
            r @?= Nothing
        , testMc "notexist" $ \c ->
            assertException 1 "Not found" $ McIO.delete "notexist" c
        ]
    , testGroup "Maybe module"
        [ testMc "foo" $ \c -> do
            b <- McMaybe.delete "foo" c
            r <- McMaybe.get "foo" c
            b @?= True
            r @?= Nothing
        , testMc "notexist" $ \c -> do
            b <- McMaybe.delete "notexist" c
            r <- McMaybe.get    "notexist" c
            b @?= False
            r @?= Nothing
        ]
    ]

-- https://code.google.com/p/memcached/wiki/ReleaseNotes1417
testIncrDecr :: Version -> Test
testIncrDecr v = testGroup "increment/decrement"
    [ testGroup "IO module"
        [ testGroup "increment"
            [ testMc "foo" $ \c ->
                assertException 6 "Non-numeric server-side value for incr or decr" $
                    McIO.increment 0 "foo" 10 10 c
            , testMc "bar" $ \c -> do
                a <- McIO.increment 0 "bar" 10 10 c
                b <- McIO.get_ "bar" c
                a @?= 1234567900
                b @?= "1234567900"
            , testMc "notexist" $ \c -> do
                a <- McIO.increment 0 "notexist" 10 10 c
                b <- McIO.get_ "notexist" c
                a @?= 10
                when (v >= ev) $ b @?= "10"
            ]
        , testGroup "decrement"
            [ testMc "foo" $ \c ->
                assertException 6 "Non-numeric server-side value for incr or decr" $
                    McIO.decrement 0 "foo" 10 10 c
            , testMc "bar" $ \c -> do
                a <- McIO.decrement 0 "bar" 10 10 c
                b <- McIO.get_ "bar" c
                a @?= 1234567880
                b @?= "1234567880"
            , testMc "notexist" $ \c -> do
                a <- McIO.decrement 0 "notexist" 10 10 c
                b <- McIO.get_ "notexist" c
                a @?= 10
                when (v >= ev) $ b @?= "10"
            ]
        ]
    , testGroup "Maybe module"
        [ testGroup "increment"
            [ testMc "foo" $ \c -> do
                r <- McMaybe.increment 0 "foo" 10 10 c
                b <- McIO.get_ "foo" c
                r @?= Nothing
                b @?= "foovalue"
            , testMc "bar" $ \c -> do
                a <- McMaybe.increment 0 "bar" 10 10 c
                b <- McIO.get_ "bar" c
                a @?= Just 1234567900
                b @?= "1234567900"
            , testMc "notexist" $ \c -> do
                a <- McMaybe.increment 0 "notexist" 10 10 c
                b <- McIO.get_ "notexist" c
                a @?= Just 10
                when (v >= ev) $ b @?= "10"
            ]
        , testGroup "decrement'"
            [ testMc "foo" $ \c -> do
                r <- McMaybe.decrement 0 "foo" 10 10 c
                b <- McIO.get_ "foo" c
                r @?= Nothing
                b @?= "foovalue"
            , testMc "bar" $ \c -> do
                a <- McMaybe.decrement 0 "bar" 10 10 c
                b <- McIO.get_ "bar" c
                a @?= Just 1234567880
                b @?= "1234567880"
            , testMc "notexist" $ \c -> do
                a <- McMaybe.decrement 0 "notexist" 10 10 c
                b <- McIO.get_ "notexist" c
                a @?= Just 10
                when (v >= ev) $ b @?= "10"
            ]
        ]
    ]
  where
    ev = Version [1,4,17] []

testFlush :: Test
testFlush = testGroup "flush"
    [ testGroup "IO module"
        [ testMc "flushAll" $ \c -> do
            McIO.flushAll c
            a <- McMaybe.get "foo" c
            a @?= Nothing
        ]
    , testGroup "Maybe module"
        [ testMc "flushAll" $ \c -> do
            r <- McMaybe.flushAll c
            a <- McMaybe.get "foo" c
            r @?= True
            a @?= Nothing
        ]
    ]

testVersion :: Test
testVersion = testGroup "version"
    [ testGroup "versionString"
        [ testMc "IO module" $ \c -> do
            v <- McIO.versionString c
            assertBool (show v ++ " is not version like.") $ isVersionLike v
        , testMc "Maybe module" $ \c -> do
            Just v <- McMaybe.versionString c
            assertBool (show v ++ " is not version like.") $ isVersionLike v
        ]
    , testGroup "version"
        [ testMc "IO module" $ \c -> do
            v <- McIO.version c
            assertEqual "version branch length" 3 (length $ versionBranch v)
        ]
    ]
  where
    isVersionLike s0 = isJust $ do
        (_, s1) <- S.readInt s0
        unless (S.head s1 == '.') Nothing
        (_, s2) <- S.readInt (S.tail s1)
        unless (S.head s2 == '.') Nothing
        (_, s3) <- S.readInt (S.tail s2)
        unless (S.null s3) Nothing

testNoOp :: Test
testNoOp = testGroup "noOp"
    [ testMc "IO module" McIO.noOp
    , testMc "Maybe module" $ \c -> do
        b <- McMaybe.noOp c
        b @?= True
    ]

testAppendPrepend :: Test
testAppendPrepend = testGroup "append/prepend"
    [ testGroup "IO module"
        [ testGroup "append"
            [ testMc "foo" $ \c -> do
                McIO.append "foo" "!!" c
                a <- McIO.get_ "foo" c
                a @?= "foovalue!!"
            , testMc "bar" $ \c -> do
                McIO.append "bar" "!!" c
                a <- McIO.get_ "bar" c
                a @?= "1234567890!!"
            , testMc "notexist" $ \c ->
                assertException 5 "Not stored." $ McIO.append "notexist" "!!" c
            ]
        , testGroup "prepend"
            [ testMc "foo" $ \c -> do
                McIO.prepend "foo" "!!" c
                a <- McIO.get_ "foo" c
                a @?= "!!foovalue"
            , testMc "bar" $ \c -> do
                McIO.prepend "bar" "!!" c
                a <- McIO.get_ "bar" c
                a @?= "!!1234567890"
            , testMc "notexist" $ \c ->
                assertException 5 "Not stored." $ McIO.prepend "notexist" "!!" c
            ]
        ]
    , testGroup "maybe module"
        [ testGroup "append'"
            [ testMc "foo" $ \c -> do
                b <- McMaybe.append "foo" "!!" c
                a <- McIO.get_ "foo" c
                b @?= True
                a @?= "foovalue!!"
            , testMc "bar" $ \c -> do
                b <- McMaybe.append "bar" "!!" c
                a <- McIO.get_ "bar" c
                b @?= True
                a @?= "1234567890!!"
            , testMc "notexist" $ \c -> do
                b <- McMaybe.append "notexist" "!!" c
                b @?= False
            ]
        , testGroup "prepend'"
            [ testMc "foo" $ \c -> do
                b <- McMaybe.prepend "foo" "!!" c
                a <- McIO.get_ "foo" c
                b @?= True
                a @?= "!!foovalue"
            , testMc "bar" $ \c -> do
                b <- McMaybe.prepend "bar" "!!" c
                a <- McIO.get_ "bar" c
                b @?= True
                a @?= "!!1234567890"
            , testMc "notexist" $ \c -> do
                b <- McMaybe.prepend "notexist" "!!" c
                b @?= False
            ]
        ]
    ]

-- https://code.google.com/p/memcached/wiki/ReleaseNotes1414
-- https://code.google.com/p/memcached/issues/detail?id=275
testTouchGAT :: Version -> Test
testTouchGAT v = testGroup "touch/GAT"
    [ testGroup "IO module"
        [ testGroup "touch"
            [ testMc "foo" $ \c -> do
                a <- McMaybe.get_ "foo" c
                McIO.touch 1 "foo" c
                a @?= (Just "foovalue")
                when (v >= ev) $ do
                    threadDelay 1100000
                    b <- McMaybe.get_ "foo" c
                    b @?= Nothing
            , testMc "notexist" $ \c ->
                assertException 1 "Not found" $ McIO.touch 1 "notexist" c
            ]
        , testGroup "getAndTouch"
            [ testMc "foo" $ \c -> do
                x <- McMaybe.get "foo" c
                y <- McIO.getAndTouch 1 "foo" c
                x @?= Just (0, "foovalue")
                y @?= (0, "foovalue")
                when (v >= ev) $ do
                    threadDelay 1100000
                    z <- McMaybe.get "foo" c
                    z @?= Nothing
            , testMc "notexist" $ \c ->
                assertException 1 "Not found" $ McIO.getAndTouch 1 "notexist" c
            ]
        , testGroup "getAndTouch_"
            [ testMc "foo" $ \c -> do
                x <- McMaybe.get_ "foo" c
                y <- McIO.getAndTouch_ 1 "foo" c
                x @?= Just "foovalue"
                y @?=      "foovalue"
                when (v >= ev) $ do
                    threadDelay 1100000
                    z <- McMaybe.get_ "foo" c
                    z @?= Nothing
            , testMc "notexist" $ \c ->
                assertException 1 "Not found" $ McIO.getAndTouch_ 1 "notexist" c
            ]
        ]
    , testGroup "Maybe module"
        [ testGroup "touch"
            [ testMc "foo" $ \c -> do
                a <- McMaybe.get_ "foo" c
                r <- McMaybe.touch 1 "foo" c
                a @?= (Just "foovalue")
                r @?= True
                when (v >= ev) $ do
                    threadDelay 1100000
                    b <- McMaybe.get_ "foo" c
                    b @?= Nothing
            , testMc "notexist" $ \c -> do
                r <- McMaybe.touch 1 "notexist" c
                a <- McMaybe.get "notexist" c
                r @?= False
                a @?= Nothing
            ]
        , testGroup "getAndTouch'/getMaybeAndTouch"
            [ testMc "foo" $ \c -> do
                a <- McMaybe.get "foo" c
                r <- McMaybe.getAndTouch 1 "foo" c
                a @?= Just (0, "foovalue")
                r @?= Just (0, "foovalue")
                when (v >= ev) $ do
                    threadDelay 1100000
                    b <- McMaybe.get_ "foo" c
                    b @?= Nothing
            , testMc "notexist" $ \c -> do
                r <- McMaybe.getAndTouch 1 "notexist" c
                a <- McMaybe.get "notexist" c
                r @?= Nothing
                a @?= Nothing
            ]
        , testGroup "getAndTouch'_/getMaybeAndTouch_"
            [ testMc "foo" $ \c -> do
                a <- McMaybe.get_ "foo" c
                r <- McMaybe.getAndTouch_ 1 "foo" c
                a @?= Just "foovalue"
                r @?= Just "foovalue"
                when (v >= ev) $ do
                    threadDelay 1100000
                    b <- McMaybe.get_ "foo" c
                    b @?= Nothing
            , testMc "notexist" $ \c -> do
                r <- McMaybe.getAndTouch_ 1 "notexist" c
                a <- McMaybe.get "notexist" c
                r @?= Nothing
                a @?= Nothing
            ]
        ]
    ]
  where
    ev = Version [1,4,14] []

testModify :: Test
testModify = testGroup "modify"
    [ testMc "reverse foo" $ \c -> do
        r <- McIO.modify 0 "foo" (\f v -> (f + 100, L.reverse v, v)) c
        a <- McMaybe.get "foo" c
        r @?= "foovalue"
        a @?= Just (100, "eulavoof")
    , testMc "notexist" $ \c ->
        assertException 1 "Not found" $
            McIO.modify 0 "notexist" (\f v -> (f,v,())) c
    ]

testModify_ :: Test
testModify_ = testGroup "modify_"
    [ testMc "reverse foo" $ \c -> do
        McIO.modify_ 0 "foo" (\f v -> (f + 100, L.reverse v)) c
        a <- McMaybe.get "foo" c
        a @?= Just (100, "eulavoof")
    , testMc "notexist" $ \c ->
        assertException 1 "Not found" $
            McIO.modify_ 0 "notexist" (\f v -> (f,v)) c
    ]



main :: IO ()
main = bracket startMemcached terminateProcess $ \_ -> do
    v <- McIO.withConnection def McIO.version
    defaultMain
        [ testGet
        , testSetAddReplace
        , testDelete
        , testIncrDecr v
        , testFlush
        , testVersion
        , testNoOp
        , testAppendPrepend
        , testTouchGAT v
        , testModify
        , testModify_
        ]