delta-types-1.0.0.0: test/unit/Data/Delta/EmbeddingSpec.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use lambda-case" #-}
module Data.Delta.EmbeddingSpec
( spec
) where
import Prelude
import Control.Exception
( Exception (..)
, SomeException
)
import Data.Delta.Core
( Delta (..)
, Replace (..)
, apply
)
import Data.Delta.Embedding
( Embedding' (..)
, Embedding
, fromEmbedding
, liftUpdates
, mkEmbedding
, pair
)
import Data.Delta
( Semigroupoid (o)
)
import Data.Maybe
( isJust
)
import Test.Hspec
( Spec
, describe
, it
)
import Test.QuickCheck
( Arbitrary (..)
, Property
, frequency
, forAll
, property
, (===)
, (.&&.)
)
{-----------------------------------------------------------------------------
Tests
------------------------------------------------------------------------------}
spec :: Spec
spec = do
describe "embeddingAB'" $ do
it "prop_load_write" $
prop_load_write embeddingAB'
it "prop_update_apply" $
prop_update_apply embeddingAB'
describe "fromEmbedding . mkEmbedding'" $ do
let embeddingAB'2 = fromEmbedding $ mkEmbedding embeddingAB'
it "prop_load_write" $
prop_load_write embeddingAB'2
it "prop_update_apply" $
prop_update_apply embeddingAB'2
describe "pair" $ do
let embeddingAABB = fromEmbedding $ pair embeddingAB embeddingAB
it "prop_load_write" $
prop_load_write embeddingAABB
it "prop_update_apply" $
prop_update_apply embeddingAABB
describe "liftUpdates" $ do
it "load . apply" $ property $
\(das :: [DeltaA]) (a :: A) ->
let Embedding'{load} = embeddingAB'
(b, _) = liftUpdates embeddingAB das a
in toMaybe (load b)
=== Just (apply das a)
it "apply . load" $ property $
\(das :: [DeltaA]) (a :: A) ->
let Embedding'{write} = embeddingAB'
(b, dbs) = liftUpdates embeddingAB das a
in b === apply dbs (write a)
describe "Semigroupid" $ do
let e1 = diagonal `o` embeddingAB
e2 = pair embeddingAB embeddingAB `o` diagonal
it "prop_load_write" $
prop_load_write (fromEmbedding e1)
.&&. prop_load_write (fromEmbedding e2)
it "prop_update_apply" $
prop_update_apply (fromEmbedding e1)
.&&. prop_update_apply (fromEmbedding e2)
it "diagonal commutes" $ property $
\(das :: [DeltaA]) (a :: A) ->
let Embedding'{load=load1} = fromEmbedding e1
Embedding'{load=load2} = fromEmbedding e2
(b1, _) = liftUpdates e1 das a
(b2, _) = liftUpdates e2 das a
in isJust (toMaybe (load1 b1))
.&&. toMaybe (load1 b1) === toMaybe (load2 b2)
-- | Law relating 'load' and 'write'.
prop_load_write
:: ( Arbitrary (Base da), Show (Base da), Eq (Base da) )
=> Embedding' da db -> Property
prop_load_write Embedding'{load,write} =
forAll arbitrary $ \a ->
toMaybe (load (write a)) === Just a
-- | Law relating 'update' and 'load'.
prop_update_apply
:: ( Delta da, Delta db, Arbitrary da, Arbitrary (Base da)
, Show (Base da), Show da, Eq (Base da)
)
=> Embedding' da db -> Property
prop_update_apply Embedding'{load,write,update} =
forAll arbitrary $ \a ->
forAll arbitrary $ \da ->
let b = write a
db = update a b da
in Just (apply da a)
=== toMaybe (load (apply db b))
toMaybe :: Either SomeException b -> Maybe b
toMaybe = either (const Nothing) Just
{-----------------------------------------------------------------------------
Embeddings
------------------------------------------------------------------------------}
data NoPreimage = NoPreimage
deriving Show
instance Exception NoPreimage
type A = Int
type DeltaA = Replace Int
type B = Maybe (A, Char)
data DeltaB
= ChangeNone
| ChangeA DeltaA
| ChangeChar Char
deriving Show
instance Delta DeltaB where
type Base DeltaB = B
apply ChangeNone b = b
apply (ChangeA _ ) Nothing = Nothing
apply (ChangeA da) (Just (a, c)) = Just (apply da a, c)
apply (ChangeChar _) Nothing = Nothing
apply (ChangeChar c) (Just (a, _)) = Just (a, c)
embeddingAB :: Embedding DeltaA DeltaB
embeddingAB = mkEmbedding embeddingAB'
embeddingAB' :: Embedding' DeltaA DeltaB
embeddingAB' = Embedding'
{ load = \b -> case b of
Nothing -> Left $ toException NoPreimage
Just (a,_) -> Right a
, write = \a -> Just (a, 'X')
, update = \_ _ da -> ChangeA da
}
-- | Embedding into the diagonal
diagonal :: (Delta da, Eq (Base da)) => Embedding da (da, da)
diagonal = mkEmbedding diagonal'
diagonal' :: (Delta da, Eq (Base da)) => Embedding' da (da, da)
diagonal' = Embedding'
{ load = \(a1, a2) ->
if a1 == a2
then Right a1
else Left $ toException NoPreimage
, write = \a -> (a, a)
, update = \_ _ da -> (da, da)
}
{-----------------------------------------------------------------------------
Random generators
------------------------------------------------------------------------------}
instance Arbitrary DeltaA where
arbitrary = Replace <$> arbitrary
instance Arbitrary DeltaB where
arbitrary = frequency
[ (1, pure ChangeNone)
, (4, ChangeA <$> arbitrary)
, (4, ChangeChar <$> arbitrary)
]