{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Control.Distributed.Process
import Control.Distributed.Process.Node
import qualified Control.Distributed.Process.Extras (__remoteTable)
import Control.Distributed.Process.Execution.Mailbox
import Control.Distributed.Process.Extras.Time
import Control.Distributed.Process.Extras.Timer
import Control.Distributed.Process.SysTest.Utils
import Control.Rematch (equalTo)
import Prelude hiding (drop)
import Data.Maybe (catMaybes)
import Test.Framework as TF (defaultMain, testGroup, Test)
import Test.Framework.Providers.HUnit
import qualified MailboxTestFilters (__remoteTable)
import MailboxTestFilters (myFilter, intFilter)
import Network.Transport.TCP
import qualified Network.Transport as NT
-- TODO: This whole test suite would be much better off using QuickCheck.
-- The test-framework driver however, doesn't have the API support we'd need
-- to wire in our tests, so we'll have to write a compatibility layer.
-- That should probably go into (or beneath) the C.D.P.P.Test module.
allBuffersShouldRespectFIFOOrdering :: BufferType -> TestResult Bool -> Process ()
allBuffersShouldRespectFIFOOrdering buffT result = do
let [a, b, c] = ["a", "b", "c"]
mbox <- createAndSend buffT [a, b, c]
active mbox acceptEverything
Just Delivery { messages = msgs } <- receiveTimeout (after 2 Seconds)
[ match return ]
let [ma', mb', mc'] = msgs
Just a' <- unwrapMessage ma' :: Process (Maybe String)
Just b' <- unwrapMessage mb' :: Process (Maybe String)
Just c' <- unwrapMessage mc' :: Process (Maybe String)
let values = [a', b', c']
stash result $ values == [a, b, c]
-- if values /= [a, b, c]
-- then liftIO $ putStrLn $ "unexpected " ++ ((show buffT) ++ (" values: " ++ (show values)))
-- else return ()
resizeShouldRespectOrdering :: BufferType
-> TestResult [String]
-> Process ()
resizeShouldRespectOrdering buffT result = do
let [a, b, c, d, e] = ["a", "b", "c", "d", "e"]
mbox <- createAndSend buffT [a, b, c, d, e]
resize mbox (3 :: Integer)
active mbox acceptEverything
Just Delivery{ messages = msgs } <- receiveTimeout (after 2 Seconds) [ match return ]
let [mc', md', me'] = msgs
Just c' <- unwrapMessage mc' :: Process (Maybe String)
Just d' <- unwrapMessage md' :: Process (Maybe String)
Just e' <- unwrapMessage me' :: Process (Maybe String)
let values = [c', d', e']
stash result $ values
bufferLimiting :: BufferType -> TestResult (Integer, [Maybe String]) -> Process ()
bufferLimiting buffT result = do
let msgs = ["a", "b", "c", "d", "e", "f", "g"]
mbox <- createMailboxAndPost buffT 4 msgs
MailboxStats{ pendingMessages = pending'
, droppedMessages = dropped'
, currentLimit = limit' } <- statistics mbox
pending' `shouldBe` equalTo 4
dropped' `shouldBe` equalTo 3
limit' `shouldBe` equalTo 4
active mbox acceptEverything
Just Delivery{ messages = recvd
, totalDropped = skipped } <- receiveTimeout (after 5 Seconds)
[ match return ]
seen <- mapM unwrapMessage recvd
stash result (skipped, seen)
mailboxIsInitiallyPassive :: TestResult Bool -> Process ()
mailboxIsInitiallyPassive result = do
mbox <- createMailbox Stack (6 :: Integer)
mapM_ (post mbox) ([1..5] :: [Int])
Nothing <- receiveTimeout (after 3 Seconds) [ matchAny return ]
notify mbox
inbound <- receiveTimeout (after 3 Seconds) [ match return ]
case inbound of
Just (NewMail _ _) -> stash result True
Nothing -> stash result False
complexMailboxFiltering :: (String, Int, Bool)
-> TestResult (String, Int, Bool)
-> Process ()
complexMailboxFiltering inputs@(s', i', b') result = do
mbox <- createMailbox Stack (10 :: Integer)
post mbox s'
post mbox i'
post mbox b'
waitForMailboxReady mbox 3
active mbox $ myFilter inputs
Just Delivery{ messages = [m1, m2, m3]
, totalDropped = _ } <- receiveTimeout (after 5 Seconds)
[ match return ]
Just s <- unwrapMessage m1 :: Process (Maybe String)
Just i <- unwrapMessage m2 :: Process (Maybe Int)
Just b <- unwrapMessage m3 :: Process (Maybe Bool)
stash result $ (s, i, b)
dropDuringFiltering :: TestResult Bool -> Process ()
dropDuringFiltering result = do
let rng = [1..50] :: [Int]
mbox <- createMailbox Stack (50 :: Integer)
mapM_ (post mbox) rng
waitForMailboxReady mbox 50
active mbox $ intFilter
Just Delivery{ messages = msgs } <- receiveTimeout (after 5 Seconds)
[ match return ]
seen <- mapM unwrapMessage msgs
stash result $ (catMaybes seen) == (filter even rng)
mailboxHandleReUse :: TestResult Bool -> Process ()
mailboxHandleReUse result = do
mbox <- createMailbox Queue (1 :: Limit)
post mbox "abc"
notify mbox
Just (NewMail mbox' _) <- receiveTimeout (after 2 Seconds)
[ match return ]
deliver mbox'
_ <- expect :: Process Delivery
stash result True
createAndSend :: BufferType -> [String] -> Process Mailbox
createAndSend buffT msgs = createMailboxAndPost buffT 10 msgs
createMailboxAndPost :: BufferType -> Limit -> [String] -> Process Mailbox
createMailboxAndPost buffT maxSz msgs = do
(cc, cp) <- newChan
mbox <- createMailbox buffT maxSz
spawnLocal $ mapM_ (post mbox) msgs >> sendChan cc ()
() <- receiveChan cp
waitForMailboxReady mbox $ min (toInteger (length msgs)) maxSz
return mbox
waitForMailboxReady :: Mailbox -> Integer -> Process ()
waitForMailboxReady mbox sz = do
sleep $ seconds 1
notify mbox
m <- receiveWait [
matchIf (\(NewMail mbox' sz') -> mbox == mbox' && sz' >= sz)
(\_ -> return True)
, match (\(NewMail _ _) -> return False)
, matchAny (\_ -> return False)
]
case m of
True -> return ()
False -> waitForMailboxReady mbox sz
myRemoteTable :: RemoteTable
myRemoteTable =
Control.Distributed.Process.Execution.Mailbox.__remoteTable $
Control.Distributed.Process.Extras.__remoteTable $
MailboxTestFilters.__remoteTable initRemoteTable
tests :: NT.Transport -> IO [Test]
tests transport = do
{- verboseCheckWithResult stdArgs -}
localNode <- newLocalNode transport myRemoteTable
return [
testGroup "Dequeue/Pop Ordering"
[
testCase "Queue Ordering"
(delayedAssertion
"Expected the Queue to offer FIFO ordering"
localNode True (allBuffersShouldRespectFIFOOrdering Queue))
, testCase "Stack Ordering"
(delayedAssertion
"Expected the Queue to offer FIFO ordering"
localNode True (allBuffersShouldRespectFIFOOrdering Stack))
, testCase "Ring Ordering"
(delayedAssertion
"Expected the Queue to offer FIFO ordering"
localNode True (allBuffersShouldRespectFIFOOrdering Ring))
]
, testGroup "Resize & Ordering"
[
testCase "Queue Drops Eldest"
(delayedAssertion
"expected c, d, e"
localNode ["c", "d", "e"] $ resizeShouldRespectOrdering Queue)
, testCase "Stack Drops Youngest"
(delayedAssertion
"expected a, b, c"
localNode ["a", "b", "c"] $ resizeShouldRespectOrdering Stack)
, testCase "Ring Drops Youngest"
(delayedAssertion
"expected a, b, c"
localNode ["a", "b", "c"] $ resizeShouldRespectOrdering Ring)
]
, testGroup "Buffer Limits & Discarded Messages"
[
testCase "Queue Drops Eldest and Enqueues New"
(delayedAssertion
"expected d, e, f, g"
localNode ((3 :: Integer), map Just ["d", "e", "f", "g"]) $ bufferLimiting Queue)
, testCase "Stack Drops Youngest And Pushes New"
(delayedAssertion
"expected a, b, c, g"
localNode ((3 :: Integer), map Just ["a", "b", "c", "g"]) $ bufferLimiting Stack)
, testCase "Ring Rejects New Entries"
(delayedAssertion
"expected a, b, c, d"
localNode ((3 :: Integer), map Just ["a", "b", "c", "d"]) $ bufferLimiting Ring)
]
, testGroup "Notification, Activation and Delivery"
[
testCase "Mailbox is initially Passive"
(delayedAssertion
"Expected the Mailbox to remain passive until told otherwise"
localNode True mailboxIsInitiallyPassive)
, testCase "Mailbox Notifications include usable control channel"
(delayedAssertion
"Expected traffic to be relayed directly to us"
localNode True mailboxHandleReUse)
, testCase "Complex Filtering Rules"
(delayedAssertion
"Expected the relevant filters to accept our data"
localNode inputs (complexMailboxFiltering inputs))
, testCase "Filter out unwanted messages"
(delayedAssertion
"Expected only even numbers to be sent delivered"
localNode True dropDuringFiltering)
]
]
where
inputs = ("hello", 10 :: Int, True)
main :: IO ()
main = testMain $ tests
-- | Given a @builder@ function, make and run a test suite on a single transport
testMain :: (NT.Transport -> IO [Test]) -> IO ()
testMain builder = do
Right (transport, _) <- createTransportExposeInternals (defaultTCPAddr "127.0.0.1" "10501") defaultTCPParameters
testData <- builder transport
defaultMain testData