packages feed

memcache-haskell-0.0.10: test/test-memcache.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}

module Main where

import Data.Char
import Data.Word
import qualified Data.ByteString.Char8 as BS
--import Test.Framework
import Test.Framework.TH
--import Test.Framework.Providers.HUnit
import Test.Framework.Providers.QuickCheck2
import Test.QuickCheck
--import Test.QuickCheck.Arbitrary

import Network.Memcache.Class (toChunks)
import Network.Memcache.Op
import qualified Network.Memcache.Response as R

import Debug.Trace

type ValueT = BS.ByteString
type BytesT = Word64

newtype Key = Key { getKey :: BS.ByteString } deriving Show
newtype PrintableString = PrintableString { getPrintableString :: String } deriving Show

instance Arbitrary BS.ByteString where
  arbitrary = fmap BS.pack $ arbitrary `suchThat` printableNonSpace

instance Arbitrary Key where
  arbitrary = do
    bs <- fmap BS.pack $ arbitrary `suchThat` printableNonSpace
    return (Key bs)

instance Arbitrary PrintableString where
  arbitrary = do
    bs <- arbitrary `suchThat` printableNonSpace
    return (PrintableString bs)

instance Arbitrary Option where
  arbitrary = oneof [ return Noreply ]

printableNonSpace :: String -> Bool
printableNonSpace xs = not (null xs) && (and $ map (\c -> isPrint c && c /= ' ') xs)

main :: IO ()
main = $(defaultMainGenerator)

----------------------------------------------------------------

prop_toChunks_SetOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_toChunks_SetOp key flags exptime options value = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ SetOp key' flags exptime (fromIntegral $ BS.length value) value options)
   e = "set " ++ BS.unpack key' ++ " " ++ show flags ++ " " ++ show exptime ++ " " ++ (show $ BS.length value) ++ showOptions options ++ ln ++ BS.unpack value ++ ln

prop_toChunks_CasOp :: Key -> Word32 -> Word64 -> Word64 -> [Option] -> ValueT -> Bool
prop_toChunks_CasOp key flags exptime version options value = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ CasOp key' flags exptime (fromIntegral $ BS.length value) version value options)
   e = "cas " ++ BS.unpack key' ++ " " ++ show flags ++ " " ++ show exptime ++ " " ++ (show $ BS.length value) ++ " " ++ show version ++ showOptions options ++ ln ++ BS.unpack value ++ ln

prop_toChunks_AddOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_toChunks_AddOp key flags exptime options value = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ AddOp key' flags exptime (fromIntegral $ BS.length value) value options)
   e = "add " ++ BS.unpack key' ++ " " ++ show flags ++ " " ++ show exptime ++ " " ++ (show $ BS.length value) ++ showOptions options ++ ln ++ BS.unpack value ++ ln

prop_toChunks_ReplaceOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_toChunks_ReplaceOp key flags exptime options value = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ ReplaceOp key' flags exptime (fromIntegral $ BS.length value) value options)
   e = "replace " ++ BS.unpack key' ++ " " ++ show flags ++ " " ++ show exptime ++ " " ++ (show $ BS.length value) ++ showOptions options ++ ln ++ BS.unpack value ++ ln

prop_toChunks_AppendOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_toChunks_AppendOp key flags exptime options value = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ AppendOp key' flags exptime (fromIntegral $ BS.length value) value options)
   e = "append " ++ BS.unpack key' ++ " " ++ show flags ++ " " ++ show exptime ++ " " ++ (show $ BS.length value) ++ showOptions options ++ ln ++ BS.unpack value ++ ln

prop_toChunks_PrependOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_toChunks_PrependOp key flags exptime options value = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ PrependOp key' flags exptime (fromIntegral $ BS.length value) value options)
   e = "prepend " ++ BS.unpack key' ++ " " ++ show flags ++ " " ++ show exptime ++ " " ++ (show $ BS.length value) ++ showOptions options ++ ln ++ BS.unpack value ++ ln

prop_toChunks_GetOp :: [Key] -> Bool
prop_toChunks_GetOp keys = r == e
 where
   keys' = map getKey keys
   r = chunk2string (toChunks $ GetOp keys')
   e = "get " ++ BS.unpack (BS.intercalate " " keys') ++ ln

prop_toChunks_GetsOp :: [Key] -> Bool
prop_toChunks_GetsOp keys = r == e
 where
   keys' = map getKey keys
   r = chunk2string (toChunks $ GetsOp keys')
   e = "gets " ++ BS.unpack (BS.intercalate " " keys') ++ ln

prop_toChunks_DeleteOp :: Key -> [Option] -> Bool
prop_toChunks_DeleteOp key options = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ DeleteOp key' options)
   e = "delete " ++ BS.unpack key' ++ showOptions options ++ ln

prop_toChunks_IncrOp :: Key -> Word64 -> [Option] -> Bool
prop_toChunks_IncrOp key diff options = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ IncrOp key' diff options)
   e = "incr " ++ BS.unpack key' ++ " " ++ show diff ++ showOptions options ++ ln

prop_toChunks_DecrOp :: Key -> Word64 -> [Option] -> Bool
prop_toChunks_DecrOp key diff options = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ DecrOp key' diff options)
   e = "decr " ++ BS.unpack key' ++ " " ++ show diff ++ showOptions options ++ ln

prop_toChunks_TouchOp :: Key -> Word64 -> [Option] -> Bool
prop_toChunks_TouchOp key exptime options = r == e
 where
   key' = getKey key
   r = chunk2string (toChunks $ TouchOp key' exptime options)
   e = "touch " ++ BS.unpack key' ++ " " ++ show exptime ++ showOptions options ++ ln

prop_toChunks_PingOp :: Bool
prop_toChunks_PingOp = chunk2string (toChunks PingOp) == "ping" ++ ln

prop_toChunks_FlushAllOp :: Bool
prop_toChunks_FlushAllOp = chunk2string (toChunks FlushAllOp) == "flush_all" ++ ln

prop_toChunks_QuitOp :: Bool
prop_toChunks_QuitOp = chunk2string (toChunks QuitOp) == "quit" ++ ln

prop_toChunks_StatsOp :: [PrintableString] -> Bool
prop_toChunks_StatsOp args = chunk2string (toChunks $ StatsOp (map (BS.pack . getPrintableString) args)) == "stats" ++ concat (map (\s -> " " ++ getPrintableString s) args) ++ ln

--------------------------------

prop_parseOpHeader_SetOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_parseOpHeader_SetOp key flags exptime options value = parseOpHeader_SetOp "set" SetOp key flags exptime options value

prop_parseOpHeader_CasOp :: Key -> Word32 -> Word64 -> Word64 -> [Option] -> ValueT -> Bool
prop_parseOpHeader_CasOp key flags exptime version options value = op == Just (CasOp key' flags exptime (fromIntegral $ BS.length value) version "" options)
 where
   key' = getKey key
   op = parseOpHeader (BS.pack ("cas " ++ BS.unpack key' ++ " " ++ show flags ++ " " ++ show exptime ++ " " ++ show (BS.length value) ++ " " ++ show version ++ showOptions options))

prop_parseOpHeader_AddOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_parseOpHeader_AddOp key flags exptime options value = parseOpHeader_SetOp "add" AddOp key flags exptime options value

prop_parseOpHeader_ReplaceOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_parseOpHeader_ReplaceOp key flags exptime options value = parseOpHeader_SetOp "replace" ReplaceOp key flags exptime options value

prop_parseOpHeader_AppendOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_parseOpHeader_AppendOp key flags exptime options value = parseOpHeader_SetOp "append" AppendOp key flags exptime options value

prop_parseOpHeader_PrependOp :: Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
prop_parseOpHeader_PrependOp key flags exptime options value = parseOpHeader_SetOp "prepend" PrependOp key flags exptime options value

prop_parseOpHeader_GetOp :: [Key] -> Bool
prop_parseOpHeader_GetOp keys = op == if null keys then Nothing else Just (GetOp keys')
  where
   keys' = map getKey keys
   op = parseOpHeader (BS.pack ("get " ++ BS.unpack (BS.intercalate " " keys')))

prop_parseOpHeader_GetsOp :: [Key] -> Bool
prop_parseOpHeader_GetsOp keys = op == if null keys then Nothing else Just (GetsOp keys')
  where
   keys' = map getKey keys
   op = parseOpHeader (BS.pack ("gets " ++ BS.unpack (BS.intercalate " " keys')))

prop_parseOpHeader_DeleteOp :: Key -> [Option] -> Bool
prop_parseOpHeader_DeleteOp key options = op == Just (DeleteOp key' options)
 where
   key' = getKey key
   op = parseOpHeader (BS.pack ("delete " ++ BS.unpack key' ++ showOptions options))

prop_parseOpHeader_IncrOp :: Key -> Word64 -> [Option] -> Bool
prop_parseOpHeader_IncrOp key value options = op == Just (IncrOp key' value options)
 where
   key' = getKey key
   op = parseOpHeader (BS.pack ("incr " ++ BS.unpack key' ++ " " ++ show value ++ showOptions options))

prop_parseOpHeader_DecrOp :: Key -> Word64 -> [Option] -> Bool
prop_parseOpHeader_DecrOp key value options = op == Just (DecrOp key' value options)
 where
   key' = getKey key
   op = parseOpHeader (BS.pack ("decr " ++ BS.unpack key' ++ " " ++ show value ++ showOptions options))

prop_parseOpHeader_TouchOp :: Key -> Word64 -> [Option] -> Bool
prop_parseOpHeader_TouchOp key exptime options = op == Just (TouchOp key' exptime options)
 where
   key' = getKey key
   op = parseOpHeader (BS.pack ("touch " ++ BS.unpack key' ++ " " ++ show exptime ++ showOptions options))

prop_parseOpHeader_PingOp :: Bool
prop_parseOpHeader_PingOp = parseOpHeader "ping" == Just (PingOp)

prop_parseOpHeader_FlushAllOp :: Bool
prop_parseOpHeader_FlushAllOp = parseOpHeader "flush_all" == Just (FlushAllOp)

prop_parseOpHeader_VersionOp :: Bool
prop_parseOpHeader_VersionOp = parseOpHeader "version" == Just (VersionOp)

prop_parseOpHeader_QuitOp :: Bool
prop_parseOpHeader_QuitOp = parseOpHeader "quit" == Just (QuitOp)

prop_parseOpHeader_StatsOp :: [PrintableString] -> Bool
prop_parseOpHeader_StatsOp stats = parseOpHeader (BS.pack $ "stats" ++ concat (map (\s -> " " ++ getPrintableString s) stats)) == Just (StatsOp (map (BS.pack . getPrintableString) stats))

--------------------------------

prop_toChunks_Value :: Key -> Word32 -> ValueT -> Maybe Word64 -> Bool
prop_toChunks_Value key flag value version = chunk2string (toChunks $ R.Value key' flag len value version) == resp
  where
    len = fromIntegral $ BS.length value
    key' = getKey key
    resp = "VALUE " ++ BS.unpack key' ++ " " ++ show flag ++ " "++ show len ++ showVersion version ++ ln ++ BS.unpack value ++ ln
    showVersion version' = case version' of
      Just v -> " " ++ show v
      Nothing -> ""

prop_toChunks_Ok :: Bool
prop_toChunks_Ok = chunk2string (toChunks R.Ok) == "OK" ++ ln

--------------------------------

prop_parseResponseHeader_Value :: Key -> Word32 -> Word64 -> Maybe Word64 -> Bool
prop_parseResponseHeader_Value key flags len version = response == Just (R.Value key' flags len "" version)
  where
    response = R.parseResponseHeader input
    input = BS.pack $ "VALUE " ++ BS.unpack key' ++ " " ++ show flags ++ " " ++ show len ++ versionPart version
    key' = getKey key
    versionPart version' = case version' of
      Just v -> " " ++ show v
      Nothing -> ""

prop_parseResponseHeader_Ok :: Bool
prop_parseResponseHeader_Ok = R.parseResponseHeader "OK" == Just (R.Ok)

prop_parseResponseHeader_ServerError :: PrintableString -> Bool
prop_parseResponseHeader_ServerError msg = R.parseResponseHeader (BS.pack $ "SERVER_ERROR " ++ msg' ++ "\r\n") == Just (R.ServerError msg')
  where
    msg' = getPrintableString msg

prop_parseResponseHeader_ClientError :: PrintableString -> Bool
prop_parseResponseHeader_ClientError msg = R.parseResponseHeader (BS.pack $ "CLIENT_ERROR " ++ msg' ++ "\r\n") == Just (R.ClientError msg')
  where
    msg' = getPrintableString msg

prop_parseResponseHeader_Code :: Word64 -> Bool
prop_parseResponseHeader_Code code = R.parseResponseHeader (BS.pack $ show code) == Just (R.Code code)

----------------------------------------------------------------

chunk2string :: [BS.ByteString] -> String
chunk2string = BS.unpack . BS.concat

ln :: String
ln = "\r\n"

showOptions :: Show a => [a] -> String
showOptions options = (concat $ map (\o -> " " ++ show o) options)

parseOpHeader_SetOp :: String -> (BS.ByteString -> Word32 -> Word64 -> BytesT -> ValueT -> [Option] -> Op) -> Key -> Word32 -> Word64 -> [Option] -> ValueT -> Bool
parseOpHeader_SetOp cmd opType key flags exptime options value = if op == expected then True else trace ("expected: " ++ show expected ++ " but: " ++ show op) False
 where
   key' = getKey key
   expected = Just (opType key' flags exptime (fromIntegral $ BS.length value) "" options)
   op = parseOpHeader (BS.pack (cmd ++ " " ++ BS.unpack key' ++ " " ++ show flags ++ " " ++ show exptime ++ " " ++ show (BS.length value) ++ showOptions options))