packages feed

tpar-0.1.0.0: TPar/SubPubStream/Test.hs

{-# LANGUAGE ScopedTypeVariables #-}

module TPar.SubPubStream.Test where

import Control.Exception
import Control.Applicative
import Test.QuickCheck

import Control.Concurrent.STM
import Control.Monad.Trans.State
import Control.Distributed.Process
import Control.Distributed.Process.Node
import Control.Distributed.Process.Serializable
import Network.Transport.InMemory

import Pipes
import qualified Pipes.Prelude as P.P
import TPar.SubPubStream

data TestEvent a r
    = NewSubscriber (Maybe Int) (TestEvent a r) -- how long before terminating
    | Produce a (TestEvent a r)
    | Finish r
    | Throw
    deriving (Show)

instance (Arbitrary a, Arbitrary r) => Arbitrary (TestEvent a r) where
    arbitrary = oneof
        [ Produce <$> arbitrary <*> arbitrary
        , newSub
        , Finish <$> arbitrary
        , pure Throw
        ]
      where
        newSub = (\n -> NewSubscriber $ fmap getPositive n) <$> arbitrary <*> arbitrary

atomically' :: MonadIO m => STM a -> m a
atomically' = liftIO . atomically

test :: TestEvent Int () -> Property
test = ioProperty . runLocalProcess . test'

--test :: Property
--test = ioProperty $ runLocalProcess $ test' $ events'

runLocalProcess :: Process a -> IO a
runLocalProcess process = do
    tport <- createTransport
    node <- newLocalNode tport initRemoteTable
    resultVar <- atomically newEmptyTMVar
    runProcess node $ do
        r <- process
        say "runLocalProcess:done"
        atomically' $ putTMVar resultVar r

    atomically $ takeTMVar resultVar

produceTMVar :: TMVar (Either r a) -> Producer a Process r
produceTMVar chan = go
  where
    go = do
        lift $ say "produceTChan:waiting"
        mx <- liftIO $ atomically $ takeTMVar chan
        case mx of
          Right x -> lift (say "produceTChan:fed") >> yield x >> go
          Left r  -> lift (say "produceTChan:done") >> return r

test' :: forall a r. (Show a, Show r, Serializable a, Serializable r)
     => TestEvent a r -> Process Bool
test' events0 = do
    produceChan <- atomically' newEmptyTMVar
    (pubSubSrc, readRet) <- fromProducer $ produceTMVar produceChan >-> traceP
    goodVars <- execStateT (go produceChan pubSubSrc events0) []
    say $ "goodVars:"++show (length goodVars)
    and <$> mapM (atomically' . readTMVar) goodVars
  where
    go :: TMVar (Either r a)
       -> SubPubSource a r
       -> TestEvent a r
       -> StateT [TMVar Bool] Process ()
    go produceChan pubSubSrc (NewSubscriber maybeN rest) = do
        goodVar <- atomically' newEmptyTMVar
        Just prod <- lift $ subscribe pubSubSrc
        void $ lift $ spawnLocal $ do
            say "testSubscriber:starting"
            result <- P.P.toListM'
                 $  fmap Right prod
                >-> fmap Left (maybe cat P.P.take maybeN)
            say $ "testSubscriber:got:"++show result
            say $ "testSubscriber:expected:"++show rest
            -- TODO: Actually check this result
            atomically' $ putTMVar goodVar True
        modify (goodVar:)
        go produceChan pubSubSrc rest

    go produceChan pubSubSrc (Produce x rest) = do
        lift $ say "test:produce"
        atomically' $ putTMVar produceChan (Right x)
        go produceChan pubSubSrc rest

    go produceChan pubSubSrc (Finish x) = do
        lift $ say "test:finish"
        atomically' $ putTMVar produceChan (Left x)

    go produceChan pubSubSrc Throw = do
        lift $ say "test:throw"
        atomically' $ putTMVar produceChan (Right $ throw TestException)

data TestException = TestException
                   deriving (Show)
instance Exception TestException

events' :: TestEvent Int ()
events' = NewSubscriber Nothing $ Finish ()

events :: TestEvent Int ()
events =
      Produce 1
    $ NewSubscriber Nothing
    $ Produce 2
    $ Produce 3
    $ Finish ()

dbg :: MonadIO m => Show a => a -> m ()
dbg = liftIO . print

traceP :: (MonadIO m, Show a) => Pipe a a m r
traceP = P.P.mapM (\x -> dbg x >> return x)