packages feed

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