packages feed

Dflow-0.0.1: test/ArbTest.hs

{-# OPTIONS_GHC -fno-warn-orphans -XFlexibleInstances #-}


module ArbTest (
   ArbEvents (..),
   RtspTest (..),
   interpret,
   compile,
   prop_emitsBefore,
   prop_RTSP,
   prop_rtspMonoid1,
   prop_rtspMonoid2,
   prop_rtspMonoidCommutes,
   prop_rtspMonoidAssociates,
   prop_rtspCategoryId1,
   prop_rtspCategoryId2,
   prop_rtspCategoryAssociates,
   prop_rtspCategoryAssociates2,
   prop_rtspIfThenElse,
   prop_eventCount,
   prop_eventLatch
) where


-- import Control.Applicative
import Control.RTSP
import Control.Category
import Control.Monad
import Data.Function (on)
import Data.List (groupBy, sortBy, partition)
import Data.Maybe
import Data.Monoid
import Data.Ord
import Data.Time
import Test.QuickCheck


import Prelude hiding ((.), id)



-- Orphan instances for RTSP types

instance Arbitrary a => Arbitrary (Event a) where
   arbitrary = do
         dt <- arbitrary
         v <- arbitrary
         return $ Event (dt `addUTCTime` epoch) v
   shrink = shrinkNothing


instance Arbitrary NominalDiffTime where
   arbitrary = fmap (fromRational . abs) arbitrary
   shrink dt = if dt == dtSecs then [] else [dtSecs]
      where dtSecs = fromInteger $ floor dt 



-- | Base time for events.
epoch :: UTCTime
epoch = UTCTime (fromGregorian 2000 1 1) 0


-- | A list of arbitrary events in chronological order
newtype ArbEvents a = ArbEvents [Event a]

instance (Show a) => Show (ArbEvents a) where
   show (ArbEvents evs) = "[\n" ++ concatMap showEvent evs ++ "]"
      where showEvent (Event t v) = "   Event " ++ show (diffUTCTime t epoch) ++ " " ++ show v ++ "\n"

instance (Arbitrary a) => Arbitrary (ArbEvents a) where
   arbitrary = do
      times <- fmap (scanl (flip addUTCTime) epoch) arbitrary
      events <- forM times $ \t -> fmap (Event t) arbitrary
      return $ ArbEvents events
   shrink (ArbEvents evs) = map ArbEvents $ shrink evs


-- | An RTSP test consists of a descriptive string, a list function and an RTSP.  The list function
-- has the same effect on the list of events that the RTSP has on a stream.
data RtspTest a =
      Id
      | Delay Rational
      | Dup Rational
      | Func (a -> a) String
      | If (a -> Bool) String (RtspTest a) (RtspTest a)
      | Pipe (RtspTest a) (RtspTest a)
      | Par (RtspTest a) (RtspTest a)

instance Show (RtspTest a) where
   show Id = "id"
   show (Delay t) = "delay " ++ show t
   show (Dup t) = "duplicate " ++ show t
   show (Func _ str) = "stream " ++ str
   show (If _ str r1 r2) = "(ifThenElse (" ++ str ++ ", " ++ show r1 ++ ") (" ++ show r2 ++ "))"
   show (Pipe r1 r2) = "(" ++ show r1 ++ " >>> " ++ show r2 ++ ")"
   show (Par r1 r2) = "(" ++ show r1 ++ " `mappend` " ++ show r2 ++ ")"


instance (Integral a) => Arbitrary (RtspTest a) where
   arbitrary = do
      frequency [
         (2,  return Id),
         (1,  fmap (Delay . fromRational . abs) arbitrary),
         (1,  fmap (Dup . fromRational . abs) arbitrary),
         (1,  oneof [return $ Func (*2) "(*2)",
                     return $ Func (+1) "(+1)",
                     return $ Func (*3) "(*3)"]),
         (1, do
                 (p, str) <- elements [(odd, "odd"),
                                       (even, "even"),
                                       ((== 0) . (`mod` 3), "mult3")]
                 return (If p str) `ap` arbitrary `ap` arbitrary),
         (1, return Pipe `ap` arbitrary `ap` arbitrary),
         (1, return Par `ap` arbitrary `ap` arbitrary)
         ] 
   shrink Id = []
   shrink (Delay dt) = Id : map Delay (shrink dt)
   shrink (Dup dt) = Delay dt : map Dup (shrink dt)
   shrink (Func _ _) = [Id]
   shrink (If p str r1 r2) = shrinkBinaryOp (If p str) r1 r2
   shrink (Pipe r1 r2) = shrinkBinaryOp Pipe r1 r2
   shrink (Par r1 r2) = shrinkBinaryOp Par r1 r2


shrinkBinaryOp :: (Integral a) => (RtspTest a -> RtspTest a -> RtspTest a) -> RtspTest a -> RtspTest a -> [RtspTest a] 
shrinkBinaryOp op r1 r2 = concat [
   [Id, r1, r2], 
   map (\r -> op r r2) $ shrink r1,
   map (op r1) $ shrink r2]


-- | Interpret a test on a list, predicting the output for the equivalent arrow.
interpret :: (Num a) => RtspTest a -> [Event a] -> [Event a]
interpret Id             evs = evs
interpret (Delay dt)     evs = map (\(Event t v) -> Event (addUTCTime (fromRational dt) t) v) evs
interpret (Dup dt)       evs =
   foldl merge [] $ map (\(Event t v) -> [Event t v, Event (addUTCTime (fromRational dt) t) v]) evs
interpret (Func f _)     evs = map (fmap f) evs
interpret (If p _ r1 r2) evs = merge (interpret r1 thens) (interpret r2 elses)
   where 
      (thens, elses) = partition (p . eventValue) evs
interpret (Pipe r1 r2) evs = interpret r2 $ interpret r1 evs
interpret (Par r1 r2) evs = merge (interpret r1 evs) (interpret r2 evs)



-- | Merge two sorted lists of events.
merge :: [Event a] -> [Event a] -> [Event a]
merge xs [] = xs
merge [] ys = ys
merge xs@(x:xs1) ys@(y:ys1) = if y `isBefore` x then y : merge ys1 xs else x : merge ys xs1



-- | Compile a test into an arrow.
compile :: (Num a) => RtspTest a -> RTSP a a
compile Id = id
compile (Delay dt) = delay (fromRational dt)
compile (Dup dt) = accumulate $ repeatEvent [0, fromRational dt]
compile (Func f _) = stream f
compile (If p _ r1 r2) = ifThenElse (p, compile r1) (compile r2)
compile (Pipe r1 r2) = compile r1 >>> compile r2
compile (Par r1 r2) = compile r1 `mappend` compile r2


-- | Two event streams are equivalent regardless of the ordering of simultaneous events
isEquivalent :: (Ord a) => [Event a] -> [Event a] -> Bool
isEquivalent xs ys = normalise xs == normalise ys
   where normalise = map (sortBy (comparing eventValue)) . groupBy ((==) `on` eventTime)


-- | Assert that "compile" and "interpret" are equivalent.
prop_RTSP :: RtspTest Integer -> ArbEvents Integer -> Property
prop_RTSP tst (ArbEvents evs) = printTestCase failStr $ result1 `isEquivalent` result2
   where
      result1 = interpret tst evs
      result2 = simulateRTSP (compile tst) evs
      failStr = concat [  "interpret => ", show (ArbEvents result1), "\n",
                          "compile => ", show (ArbEvents result2), "\n"]
                          



-- | Reify primitive RTA actions.
data RtaTest s c =
   Modify (s -> s) | Emit (s -> c) | Pause (s -> NominalDiffTime)


instance (Arbitrary s, CoArbitrary s, Arbitrary c) => Arbitrary (RtaTest s c)
   where
      arbitrary = frequency [
         (3, return Modify `ap` arbitrary),
         (1, return Emit `ap` arbitrary),
         (5, return Pause `ap` arbitrary)
         ]
      shrink = shrinkNothing

instance Show (RtaTest s c) where
   show (Modify _) = "Modify"
   show (Emit _) = "Emit"
   show (Pause _) = "Pause"



instance (Integral s, Arbitrary s, CoArbitrary s, Arbitrary c) => Arbitrary (RTA s c Bool) where
   arbitrary = fmap execRtaTests arbitrary
   shrink = shrinkNothing

instance (CoArbitrary b, Arbitrary c) => Arbitrary (RTSP b c) where
   arbitrary = do
      rtaF <- arbitrary
      return $ execRTA (0 :: Integer) rtaF
   shrink = shrinkNothing


-- | Execute an RtaTest      
execRtaTest :: RtaTest s c -> RTA s c ()
execRtaTest (Modify f) = fmap f get >>= put
execRtaTest (Emit f) = fmap f get >>= emit
execRtaTest (Pause f) = fmap f get >>= pause


-- | Execute a sequence of RtaTests as a single action.
execRtaTests :: (Integral s) => [RtaTest s c] -> RTA s c Bool
execRtaTests ts = do
   mapM_ execRtaTest ts
   s <- get
   -- return True
   return $ (s `mod` 20) /= 0


type RtspProp = ArbEvents Integer -> Property

type RII = RTSP Integer Integer

rtspEquivalent :: (CoArbitrary b, Arbitrary c, Ord c, Show c) => RTSP b c -> RTSP b c -> ArbEvents b -> Property
rtspEquivalent r1 r2 (ArbEvents evs) = printTestCase failStr $ trace r1 `isEquivalent` trace r2
   where
      trace r = simulateRTSP r evs
      failStr = concat [
         "Trace1 = ", show $ ArbEvents $ trace r1, "\n",
         "Trace2 = ", show $ ArbEvents $ trace r2, "\n"]
         
         
prop_emitsBefore :: RII -> RII -> Event Integer -> Property
prop_emitsBefore r1 r2 ev =
   printTestCase (show (evs1, evs2)) $
   case (evs1, evs2) of
       (Nothing, Nothing) -> not (es1 `emitsBefore` es2 || es2 `emitsBefore` es1)
       (Just _,  Nothing) -> es1 `emitsBefore` es2 && not (es2 `emitsBefore` es1)
       (Nothing, Just _ ) -> not (es1 `emitsBefore` es2) && es2 `emitsBefore` es1
       (Just e1, Just e2) -> (e1 `isBefore` e2) == (es1 `emitsBefore` es2) &&
                             (e2 `isBefore` e1) == (es2 `emitsBefore` es1)
   where
      es1 = runRTSP r1 ev
      es2 = runRTSP r2 ev
      evs1 = listToMaybe $ esPeek es1
      evs2 = listToMaybe $ esPeek es2

prop_rtspMonoid1 :: RII -> RtspProp 
prop_rtspMonoid1 r = rtspEquivalent r (mempty `mappend` r)

prop_rtspMonoid2 :: RII -> RtspProp
prop_rtspMonoid2 r = rtspEquivalent r (r `mappend` mempty)


prop_rtspMonoidCommutes :: RII -> RII -> RtspProp
prop_rtspMonoidCommutes r1 r2 = rtspEquivalent (r1 `mappend` r2) (r2 `mappend` r1)

prop_rtspMonoidAssociates :: RII -> RII -> RII -> RtspProp
prop_rtspMonoidAssociates r1 r2 r3 = rtspEquivalent (r1 `mappend` (r2 `mappend` r3)) ((r1 `mappend` r2) `mappend` r3)


prop_rtspCategoryId1 :: RII -> RtspProp
prop_rtspCategoryId1 r = rtspEquivalent r (id >>> r)

prop_rtspCategoryId2 :: RII -> RtspProp
prop_rtspCategoryId2 r = rtspEquivalent r (r >>> id)


prop_rtspCategoryAssociates :: RII -> RII -> RII -> RtspProp
prop_rtspCategoryAssociates r1 r2 r3 = rtspEquivalent (r1 >>> (r2 >>> r3)) ((r1 >>> r2) >>> r3)

prop_rtspCategoryAssociates2 :: RtspTest Integer -> RtspTest Integer -> RtspTest Integer -> RtspProp
prop_rtspCategoryAssociates2 rt1 rt2 rt3 = rtspEquivalent (r1 >>> (r2 >>> r3)) ((r1 >>> r2) >>> r3)
   where
      r1 = compile rt1
      r2 = compile rt2
      r3 = compile rt3


-- | @ifThenElse (p, rThen) rElse@ is equivalent to
--
-- >  streamFilter (p, rThen) `mappend` streamFilter (not . p, rElse)
prop_rtspIfThenElse :: RII -> RII -> RtspProp
prop_rtspIfThenElse r1 r2 = rtspEquivalent
   (ifThenElse (odd, r1) r2)
   (streamFilter (odd, r1) `mappend` streamFilter (not . odd, r2))
--    where
--       r1 = compile rt1
--       r2 = compile rt2
   

traceEquivalent :: (Ord a, Show a) => [Event a] -> [Event a] -> Property
traceEquivalent trace1 trace2 = 
   printTestCase failStr $ trace1 `isEquivalent` trace2
   where failStr = concat [
            "Trace 1 = ", show $ ArbEvents trace1, "\n",
            "Trace 2 = ", show $ ArbEvents trace2, "\n"]
      
      
-- | Count events.      
eventCount :: RTSP b (Integer, b)
eventCount = execRTA 0 $ \v -> do
   s <- fmap (+1) get
   put s
   emit (s, v)
   return True

prop_eventCount :: RtspProp
prop_eventCount (ArbEvents evs) = traceEquivalent
   (simulateRTSP eventCount evs) 
   (zipWith (\n -> fmap (\v -> (n, v))) [1..] evs)



-- | Repeat each event value once a second ten times.
eventLatch :: RTSP b b
eventLatch = accumulateRTA () $ \v -> do
   replicateM_ 5 (emit v >> pause 1)
   return True
   
prop_eventLatch :: RtspProp
prop_eventLatch (ArbEvents evs) = traceEquivalent
   (simulateRTSP eventLatch evs)
   (sortBy (comparing eventTime) $ concatMap rep evs)
   where
      rep (Event t v) = [Event (n `addUTCTime` t) v | n <- [0,1..4]]