{-# LANGUAGE GeneralizedNewtypeDeriving, NoMonomorphismRestriction,
DataKinds, TypeFamilies, TemplateHaskell, ScopedTypeVariables,
MagicHash, FlexibleContexts #-}
import Test.Tasty
import Test.Tasty.HUnit
import Control.Monad.Trans.Class
import qualified Data.Functor.Identity as I
import qualified Control.Monad.Trans.Reader as R
import qualified Control.Monad.Trans.Writer as W
import Control.Monad.Classes
import Control.Monad.Classes.Run
import Control.Applicative
import Control.Exception hiding (throw)
import Data.Lens.Light
import Data.Proxy
import GHC.Prim (Proxy#, proxy#)
-- for IO tests
import qualified Foreign.Storable as Foreign
import qualified Foreign.Marshal.Alloc as Foreign
-- for monad-control tests
import qualified Data.Conduit as C
import Control.Monad.Morph
-- for zoom tests
data Record = Record
{ _listL :: [Int]
, _intL :: Int
}
deriving (Show, Eq)
makeLens ''Record
main = defaultMain tests
tests :: TestTree
tests = testGroup "Tests"
[ readerTests
, simpleStateTests
, twoStatesTests
, liftingTest
, localState
, exceptTests
, execTests
, zoomTests
, liftNTests
, liftConduitTest
, mapWriterTest
, readStateTest
, polymorphicTests
]
readerTests = testGroup "Reader Tests"
[ testCase "ask" $
let base = 5 :: Integer
power = 3 :: Int
expected = 125 :: Integer
in (runReader power action) base @?= expected
, testCase "local ask" $
let base = 5 :: Integer
power = 2 :: Int
altBase = 7 :: Integer
altPower = 3 :: Int
expected = 174 :: Integer
action' = do
x <- local (const altBase) action
y <- local (const altPower) action
pure (x + y)
in (runReader power action') base @?= expected
]
where
f = (^) :: Integer -> Int -> Integer
action = f <$> ask <*> ask
simpleStateTests = testGroup "Simple State"
[ testCase "get" $
(run $ runStateLazy (0 :: Int) get) @?= (0 :: Int, 0 :: Int)
, testCase "put" $
(run $ runStateLazy (0 :: Int) (put (1 :: Int))) @?= ((), 1 :: Int)
, testCase "put-get-put" $
(run $ runStateLazy (0 :: Int) (put (1 :: Int) *> get <* put (2 :: Int))) @?= (1 :: Int, 2 :: Int)
]
twoStatesComp = put 'b' >> put True >> put 'c'
twoStatesTests = testCase "Two States" $
(run $ runStateLazy 'a' $ runStateLazy False twoStatesComp) @?= (((), True), 'c')
newtype Foo m a = Foo { runFoo :: m a }
deriving (Functor, Applicative, Monad)
instance MonadTrans Foo where
lift = Foo
type instance CanDo (Foo m) eff = False
liftingTest = testCase "Lifting through an unknown transformer" $
(run $ runStateLazy 'a' $ runFoo $ runStateLazy False twoStatesComp) @?= (((), True), 'c')
localState = testCase "MonadLocal StateT" $
(run $ evalStateStrict 'a' $
do
s1 <- get
(s2,s3) <- local (toEnum . (+1) . fromEnum :: Char -> Char) $ do
s2 <- get
put 'x'
s3 <- get
return (s2,s3)
s4 <- get
return [s1,s2,s3,s4]) @?= "abxa"
exceptTests = testGroup "Except"
[ testCase "Catch before IO" $ do
r <- runExcept $ runStateStrict False $ throw $ ErrorCall "foo"
(r :: Either ErrorCall ((), Bool)) @?= Left (ErrorCall "foo")
, testCase "Let escape to IO" $ do
r <- try $ runExcept $ runStateStrict False $ throw UserInterrupt
(r :: Either AsyncException (Either ErrorCall ((), Bool))) @?= Left UserInterrupt
]
execTests = testCase "Exec" $ do
r <- runWriterStrict $ exec $
Foreign.alloca $ \ptr -> do
Foreign.poke ptr True
Foreign.peek ptr
r @?= (True, ())
zoomTests = testCase "Zoom" $ do
((4, [2,5], 6), Record [2,5,10] 6) @?=
(run $ runStateStrict (Record [2] 4) $ runZoom (vanLaarhoven intL) $ runZoom (vanLaarhoven listL) $ do
(s0 :: Int) <- get
tell [5 :: Int]
(s1 :: [Int]) <- ask
put (6 :: Int)
(s2 :: Int) <- ask
tell [10 :: Int]
return (s0, s1, s2)
)
liftNTests = testCase "liftN" $ do
(run $ runReader 'a' $ runReader 'b' $ runReader 'c' $
liftN (proxy# :: Proxy# (Succ Zero)) R.ask)
@?= 'b'
liftConduit
:: forall m n effM eff i o r .
( n ~ Find eff m
, MonadLiftN n m
, effM ~ Down n m
, Monad effM
)
=> Proxy# eff
-> C.ConduitM i o effM r
-> C.ConduitM i o m r
liftConduit _ = C.transPipe (liftN (proxy# :: Proxy# n))
liftConduitTest = testCase "lift conduit" $
(let
src :: C.Source I.Identity Int
src = C.yield 1 >> C.yield 2
sink :: C.Sink Int (W.Writer [Int]) ()
sink =
C.await >>=
maybe (return ()) (\x -> do lift $ tell [x::Int]; sink)
in
W.execWriter $ C.transPipe (liftN (proxy# :: Proxy# (Succ Zero))) src C.$$ sink
) @?= [1,2]
{-
execWriterStrict $ runReader (3 :: Int) $
liftConduit (proxy# :: Proxy# (EffReader Int))
(do
x <- ask
liftConduit (C.yield x)
liftConduit (C.yield (x :: Int)))
C.$$
(proxy# :: Proxy# (EffWriter String)) (do C.awaitForever $ \y -> tell (show (y :: Int) ++ "\n")))
@?= ""-}
mapWriterTest = testCase "mapWriter" $ do
run (execWriterStrict $ mapWriter (\(w :: Char) -> [w]) $ do { tell 'a'; tell 'b'; tell 'c' }) @?= "abc"
readStateTest = testCase "ReadState" $ do
let
a1 :: MonadReader Char m => m Char
a1 = ask
a2 :: MonadState Char m => m Char
a2 = runReadState (Proxy :: Proxy Char) a1
run (evalStateStrict 'w' a2) @?= 'w'
polymorphicTests = testGroup "Polymorphic monadic values"
[ testCase "MonadReader WriterT" $ do
run (runReader 'c' (W.runWriterT polyReader1)) @?= ('c', ())
, testCase "MonadReader ReaderT" $ do
run (runReader 'c' (runReader False polyReader2)) @?= 'c'
]
where
polyReader1 :: MonadReader Char m => W.WriterT () m Char
polyReader1 = ask
polyReader2 :: MonadReader Char m => R.ReaderT Bool m Char
polyReader2 = ask