module Main where import Test.Framework import Test.Framework.Providers.HUnit import Test.HUnit import Control.DeepSeq import Control.Monad.Catch import Control.Monad.Fix import Control.Monad.IO.Class import Data.Typeable import GHC.Generics (Generic) import Control.Concurrent.MVar import Control.Wire import Control.Wire.Unsafe.Event import Data.Either import Game.GoreAndAsh.Async import Game.GoreAndAsh.Core import Prelude hiding (id, (.)) import qualified Data.Sequence as S -- | Application monad is monad stack build from given list of modules over base monad (IO or Identity) type AppStack = ModuleStack [AsyncT, AsyncT] IO newtype AppState = AppState (ModuleState AppStack) deriving (Generic) instance NFData AppState -- | Wrapper around type family newtype AppMonad a = AppMonad (AppStack a) deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadThrow, MonadCatch, MonadAsync) instance GameModule AppMonad AppState where type ModuleState AppMonad = AppState runModule (AppMonad m) (AppState s) = do (a, s') <- runModule m s return (a, AppState s') newModuleState = AppState <$> newModuleState withModule _ = withModule (Proxy :: Proxy AppStack) cleanupModule (AppState s) = cleanupModule s -- | Arrow that is build over the monad stack type AppWire a b = GameWire AppMonad a b data TestException = TestException deriving Show instance Exception TestException main :: IO () main = withModule (Proxy :: Proxy AppMonad) $ do defaultMainWithOpts [ testGroup "async actions" [ testCase "simple" asyncSimple , testCase "except" asyncExcept , testCase "except no catch" asyncExcept' , testCase "cancel" asyncCancel , testCase "cancel delayed" asyncCancelDelayed , testCase "factory" asyncFactory , testCase "factory ex normal" asyncFactoryEx , testCase "factory ex except" asyncFactoryEx' ] , testGroup "async bound actions" [ testCase "simple" asyncBoundSimple , testCase "except" asyncBoundExcept , testCase "except no catch" asyncBoundExcept' , testCase "cancel" asyncCancelBound , testCase "cancel delayed" asyncCancelBoundDelayed , testCase "factory" asyncFactoryBound , testCase "factory ex normal" asyncFactoryBoundEx , testCase "factory ex except" asyncFactoryBoundEx' ] , testGroup "async sync actions" [ testCase "simple" asyncSyncSimple , testCase "except" asyncSyncExcept , testCase "except no catch" asyncSyncExcept' , testCase "cancel" asyncCancelSync , testCase "factory" asyncFactorySync , testCase "factory ex normal" asyncSyncFactoryEx , testCase "factory ex except" asyncSyncFactoryEx' ] ] mempty -- | Runs wire n times and return it result runWire :: Int -> AppWire () a -> IO (Maybe a) runWire n w = do gs <- newGameState w go gs n Nothing where go gs i ma = if i <= 0 then return ma else do (ma', gs') <- stepGame gs (return ()) go gs' (i-1) ma' asyncSimple :: Assertion asyncSimple = do ma <- runWire 100 w assertEqual "wire switch" (Just True) ma where w = proc _ -> do e <- asyncAction (return True) -< () rSwitch (pure False) -< ((), pure <$> e) asyncExcept :: Assertion asyncExcept = do ma <- runWire 100 w assertBool "returned is exception" (check ma) where check ma = case ma of Nothing -> False Just ea -> isLeft ea w :: AppWire () (Either SomeException Bool) w = proc _ -> do e <- asyncActionEx (throwM TestException) -< () rSwitch (pure $ Right False) -< ((), pure <$> e) asyncCancel :: Assertion asyncCancel = do ma <- runWire 100 w assertEqual "wire switch" (Just False) ma where w = proc _ -> do ce <- now -< () e <- asyncActionC (return True) -< ce rSwitch (pure False) -< ((), pure <$> e) asyncCancelDelayed :: Assertion asyncCancelDelayed = do ma <- runWire 100 w0 assertEqual "wire switch" (Just False) ma where w0 = switch $ proc _ -> do evar <- liftGameMonadEvent1 (const $ liftIO newEmptyMVar) . now -< () returnA -< (False, w <$> evar) w var = proc _ -> do ce <- delay NoEvent . now -< () e <- asyncActionC (io var) -< ce liftGameMonadEvent1 (const . liftIO $ putMVar var ()) -< ce rSwitch (pure False) -< ((), pure <$> e) io var = do _ <- readMVar var return True asyncFactory :: Assertion asyncFactory = do ma <- runWire 100 w assertEqual "summed values" (Just 20) ma where w = proc _ -> do eio <- wgen -< () eas <- asyncActionFactory -< eio hold . accumE (\i as -> i + sum as) 0 -< eas wgen :: AppWire () (Event (S.Seq (IO Int))) wgen = go 2 10 where go k 0 = never go k n = mkSFN $ \_ -> (Event $ S.replicate k (return 1), go k (n-1)) seqRights :: S.Seq (Either a b) -> S.Seq b seqRights = fmap fromRight . S.filter isRight where fromRight (Left _) = error "seqRights" fromRight (Right a) = a asyncFactoryEx :: Assertion asyncFactoryEx = do ma <- runWire 100 w assertEqual "summed values" (Just 20) ma where w = proc _ -> do eio <- wgen -< () eas <- asyncActionFactoryEx -< eio hold . accumE foldResult 0 -< eas foldResult :: Int -> S.Seq (Either SomeException Int) -> Int foldResult i eas = i + sum (seqRights eas) wgen :: AppWire () (Event (S.Seq (IO Int))) wgen = go 2 10 where go k 0 = never go k n = mkSFN $ \_ -> (Event $ S.replicate k (return 1), go k (n-1)) asyncFactoryEx' :: Assertion asyncFactoryEx' = do ma <- runWire 100 w assertEqual "summed values" (Just 0) ma where w = proc _ -> do eio <- wgen -< () eas <- asyncActionFactoryEx -< eio hold . accumE foldResult 0 -< eas foldResult :: Int -> S.Seq (Either SomeException Int) -> Int foldResult i eas = i + sum (seqRights eas) wgen :: AppWire () (Event (S.Seq (IO Int))) wgen = go 2 10 where go k 0 = never go k n = mkSFN $ \_ -> (Event $ S.replicate k (throwM TestException), go k (n-1)) asyncExcept' :: Assertion asyncExcept' = do ma <- (fmap Right $ runWire 100 w) `catchAll` (return . Left) assertBool "returned is exception" (isLeft ma) where w :: AppWire () Bool w = proc _ -> do e <- asyncAction (throwM TestException) -< () rSwitch (pure False) -< ((), pure <$> e) asyncBoundSimple :: Assertion asyncBoundSimple = do ma <- runWire 100 w assertEqual "wire switch" (Just True) ma where w = proc _ -> do e <- asyncActionBound (return True) -< () rSwitch (pure False) -< ((), pure <$> e) asyncBoundExcept :: Assertion asyncBoundExcept = do ma <- runWire 100 w assertBool "returned is exception" (check ma) where check ma = case ma of Nothing -> False Just ea -> isLeft ea w :: AppWire () (Either SomeException Bool) w = proc _ -> do e <- asyncActionBoundEx (throwM TestException) -< () rSwitch (pure $ Right False) -< ((), pure <$> e) asyncBoundExcept' :: Assertion asyncBoundExcept' = do ma <- (fmap Right $ runWire 100 w) `catchAll` (return . Left) assertBool "returned is exception" (isLeft ma) where w :: AppWire () Bool w = proc _ -> do e <- asyncActionBound (throwM TestException) -< () rSwitch (pure False) -< ((), pure <$> e) asyncCancelBound :: Assertion asyncCancelBound = do ma <- runWire 100 w assertEqual "wire switch" (Just False) ma where w = proc _ -> do ce <- now -< () e <- asyncActionBoundC (return True) -< ce rSwitch (pure False) -< ((), pure <$> e) asyncCancelBoundDelayed :: Assertion asyncCancelBoundDelayed = do ma <- runWire 100 w0 assertEqual "wire switch" (Just False) ma where w0 = switch $ proc _ -> do evar <- liftGameMonadEvent1 (const $ liftIO newEmptyMVar) . now -< () returnA -< (False, w <$> evar) w var = proc _ -> do ce <- delay NoEvent . now -< () e <- asyncActionBoundC (io var) -< ce liftGameMonadEvent1 (const . liftIO $ putMVar var ()) -< ce rSwitch (pure False) -< ((), pure <$> e) io var = do _ <- readMVar var return True asyncFactoryBound :: Assertion asyncFactoryBound = do ma <- runWire 100 w assertEqual "summed values" (Just 20) ma where w = proc _ -> do eio <- wgen -< () eas <- asyncActionBoundFactory -< eio hold . accumE (\i as -> i + sum as) 0 -< eas wgen :: AppWire () (Event (S.Seq (IO Int))) wgen = go 2 10 where go k 0 = never go k n = mkSFN $ \_ -> (Event $ S.replicate k (return 1), go k (n-1)) asyncFactoryBoundEx :: Assertion asyncFactoryBoundEx = do ma <- runWire 100 w assertEqual "summed values" (Just 20) ma where w = proc _ -> do eio <- wgen -< () eas <- asyncActionBoundFactoryEx -< eio hold . accumE foldResult 0 -< eas foldResult :: Int -> S.Seq (Either SomeException Int) -> Int foldResult i eas = i + sum (seqRights eas) wgen :: AppWire () (Event (S.Seq (IO Int))) wgen = go 2 10 where go k 0 = never go k n = mkSFN $ \_ -> (Event $ S.replicate k (return 1), go k (n-1)) asyncFactoryBoundEx' :: Assertion asyncFactoryBoundEx' = do ma <- runWire 100 w assertEqual "summed values" (Just 0) ma where w = proc _ -> do eio <- wgen -< () eas <- asyncActionBoundFactoryEx -< eio hold . accumE foldResult 0 -< eas foldResult :: Int -> S.Seq (Either SomeException Int) -> Int foldResult i eas = i + sum (seqRights eas) wgen :: AppWire () (Event (S.Seq (IO Int))) wgen = go 2 10 where go k 0 = never go k n = mkSFN $ \_ -> (Event $ S.replicate k (throwM TestException), go k (n-1)) asyncSyncSimple :: Assertion asyncSyncSimple = do ma <- runWire 100 w assertEqual "wire switch" (Just True) ma where w = proc _ -> do e <- asyncSyncAction (return True) -< () rSwitch (pure False) -< ((), pure <$> e) asyncSyncExcept :: Assertion asyncSyncExcept = do ma <- runWire 100 w assertBool "returned is exception" (check ma) where check ma = case ma of Nothing -> False Just ea -> isLeft ea w :: AppWire () (Either SomeException Bool) w = proc _ -> do e <- asyncSyncActionEx (throwM TestException) -< () rSwitch (pure $ Right False) -< ((), pure <$> e) asyncSyncExcept' :: Assertion asyncSyncExcept' = do ma <- (fmap Right $ runWire 100 w) `catchAll` (return . Left) assertBool "returned is exception" (isLeft ma) where w :: AppWire () Bool w = proc _ -> do e <- asyncSyncAction (throwM TestException) -< () rSwitch (pure False) -< ((), pure <$> e) asyncCancelSync :: Assertion asyncCancelSync = do ma <- runWire 100 w assertEqual "wire switch" (Just False) ma where w = proc _ -> do ce <- now -< () e <- asyncSyncActionC (return True) -< ce rSwitch (pure False) -< ((), pure <$> e) asyncFactorySync :: Assertion asyncFactorySync = do ma <- runWire 100 w assertEqual "summed values" (Just 20) ma where w = proc _ -> do eio <- wgen -< () eas <- asyncSyncActionFactory -< eio hold . accumE (\i as -> i + sum as) 0 -< eas wgen :: AppWire () (Event (S.Seq (IO Int))) wgen = go 2 10 where go k 0 = never go k n = mkSFN $ \_ -> (Event $ S.replicate k (return 1), go k (n-1)) asyncSyncFactoryEx :: Assertion asyncSyncFactoryEx = do ma <- runWire 100 w assertEqual "summed values" (Just 20) ma where w = proc _ -> do eio <- wgen -< () eas <- asyncSyncActionFactoryEx -< eio hold . accumE foldResult 0 -< eas foldResult :: Int -> S.Seq (Either SomeException Int) -> Int foldResult i eas = i + sum (seqRights eas) wgen :: AppWire () (Event (S.Seq (IO Int))) wgen = go 2 10 where go k 0 = never go k n = mkSFN $ \_ -> (Event $ S.replicate k (return 1), go k (n-1)) asyncSyncFactoryEx' :: Assertion asyncSyncFactoryEx' = do ma <- runWire 100 w assertEqual "summed values" (Just 0) ma where w = proc _ -> do eio <- wgen -< () eas <- asyncSyncActionFactoryEx -< eio hold . accumE foldResult 0 -< eas foldResult :: Int -> S.Seq (Either SomeException Int) -> Int foldResult i eas = i + sum (seqRights eas) wgen :: AppWire () (Event (S.Seq (IO Int))) wgen = go 2 10 where go k 0 = never go k n = mkSFN $ \_ -> (Event $ S.replicate k (throwM TestException), go k (n-1))