stable-tree-0.6.0: tests/TestAll.hs
{-# LANGUAGE LambdaCase #-}
module Main
( main
) where
import qualified Data.StableTree as ST
import qualified Data.StableTree.Persist as SP
import Data.StableTree ( StableTree )
import Data.StableTree.Persist ( Fragment(..), Error(..) )
import qualified Data.Map as Map
import qualified Data.Set as Set
import Control.Arrow ( first )
import Control.Applicative ( (<$>) )
import Control.Monad.State.Strict ( State, runState, modify, gets )
import Data.ByteString ( ByteString )
import Data.ByteString.Arbitrary ( ArbByteString(..) )
import Data.Map ( Map )
import Data.ObjectID ( ObjectID )
import Data.Serialize ( Serialize, encode, decode )
import Data.Text ( Text )
import Test.Tasty
import Test.Tasty.QuickCheck ( Arbitrary, testProperty, oneof, arbitrary )
import Test.QuickCheck ( Gen, elements )
-- import Debug.Trace ( trace )
-- trace :: a -> b -> b
-- trace _ b = b
main :: IO ()
main = defaultMain $
testGroup "StableTree"
[ testGroup "Pure"
[ testProperty "Int/Int" int_int
, testProperty "Float/Int" float_int
, testProperty "ByteString/Int" bytestring_int
]
, testGroup "Stored"
[ testProperty "Int/Int" store_int_int
, testProperty "Float/Int" store_float_int
, testProperty "ByteString/Int" store_bytestring_int
]
]
where
int_int :: [(Int,Int)] -> Gen Bool
int_int pairs =
let m = Map.fromList pairs
st = ST.fromMap m
in if m == ST.toMap st && Map.keys m == ST.keys st && Map.elems m == ST.elems st
then do
and <$> sequence [ test_delete pairs
, return $ test_lookup (Map.toList m) st
, test_insert pairs
, test_mutate st ]
else return False
float_int :: [(Float,Int)] -> Gen Bool
float_int pairs =
let m = Map.fromList pairs
st = ST.fromMap m
in if m == ST.toMap st && Map.keys m == ST.keys st && Map.elems m == ST.elems st
then do
and <$> sequence [ test_delete pairs
, return $ test_lookup (Map.toList m) st
, test_insert pairs
, test_mutate st ]
else return False
bytestring_int :: [(ArbByteString,Int)] -> Gen Bool
bytestring_int pairs =
let p' = map (first fromABS) pairs
m = Map.fromList p'
st = ST.fromMap m
in if m == ST.toMap st && Map.keys m == ST.keys st && Map.elems m == ST.elems st
then do
and <$> sequence [ test_delete p'
, return $ test_lookup (Map.toList m) st
, test_insert p' ]
else return False
test_lookup :: (Ord k, Show k, Eq v, Show v) => [(k,v)] -> StableTree k v -> Bool
test_lookup [] _ = True
test_lookup ((k,v):rest) t =
case ST.lookup k t of
Just v' | v' == v -> test_lookup rest t
_ -> False
test_delete :: (Show k, Ord k, Serialize k, Show v, Serialize v)
=> [(k,v)]
-> Gen Bool
test_delete [] = return True
test_delete kvs = do
delkey <- elements (map fst kvs)
let m = Map.fromList kvs
m' = Map.delete delkey m
s = ST.fromMap m
s' = ST.delete delkey s
return (s' == ST.fromMap m')
test_insert :: (Show k, Ord k, Serialize k, Show v, Serialize v)
=> [(k,v)]
-> Gen Bool
test_insert [] = return True
test_insert kvs = do
(inskey, insval) <- elements kvs
let kvs' = [(k,v) | (k,v) <- kvs, k /= inskey]
m = Map.fromList kvs'
m' = Map.insert inskey insval m
s = ST.fromMap m
s' = ST.insert inskey insval s
return (s' == ST.fromMap m')
test_mutate :: (Arbitrary k, Show k, Ord k, Serialize k, Arbitrary v, Show v, Serialize v)
=> StableTree k v
-> Gen Bool
test_mutate tree | ST.size tree == 0 = return True
test_mutate tree = mutate (10::Int) (Set.fromList $ ST.keys tree) tree tree
where
mutate 0 _ reference accum = return $ reference == accum
mutate n keys ref accum = do
accum' <- oneof [insert, delete]
mutate (n-1) keys ref accum'
where
insert = do
key <- arbitrary
if Set.member key keys
then insert
else do
val <- arbitrary
return $ ST.delete key $ ST.insert key val accum
delete = do
key <- elements $ ST.keys accum
let Just val = ST.lookup key accum
return $ ST.insert key val $ ST.delete key accum
store_int_int :: [(Int,Int)] -> Bool
store_int_int = action
store_float_int :: [(Float,Int)] -> Bool
store_float_int = action
store_bytestring_int :: [(ArbByteString,Int)] -> Bool
store_bytestring_int = action . map (first fromABS)
action :: (Eq k, Ord k, Serialize k, Eq v, Serialize v) => [(k,v)] -> Bool
action pairs = fst $ runState go Map.empty
where
go = do
let m = Map.fromList pairs
st = ST.fromMap m
Right tid <- SP.store' store st
Right st' <- SP.load' load tid
return $ m == ST.toMap st'
-- |Error type for RAM storage. Not a lot can go wrong in RAM...
data RamError = NotFound ObjectID
| SerializationError String
| ApiError Text
deriving ( Show )
instance Error RamError where
stableTreeError = ApiError
type StableTreeState = State (Map ByteString ByteString)
store :: (Ord k, Serialize k, Serialize v)
=> ObjectID -> Fragment k v -> StableTreeState (Maybe RamError)
store oid frag = do
modify $ Map.insert (encode oid) (encode frag)
return Nothing
load :: (Ord k, Serialize k, Serialize v)
=> ObjectID -> StableTreeState (Either RamError (Fragment k v))
load oid =
gets (Map.lookup $ encode oid) >>= \case
Nothing -> return $ Left $ NotFound oid
Just fragBS ->
case decode fragBS of
Left err -> return $ Left $ SerializationError err
Right frag -> return $ Right frag