packages feed

order-maintenance-0.2.0.0: src/test-suites/TestSuite.hs

module TestSuite (

    tests

) where

-- Control

import           Control.Monad
import           Control.Monad.ST
import           Control.Monad.Trans.Class
import           Control.Monad.Trans.State

-- Data

import           Data.Set (Set)
import qualified Data.Set as Set
import           Data.Map (Map)
import qualified Data.Map as Map
import           Data.Order.Algorithm (Algorithm, withRawAlgorithm)
import qualified Data.Order.Algorithm as Algorithm
import           Data.Order.Algorithm.Raw

-- Test

import Test.QuickCheck

-- Distribution

import Distribution.TestSuite
import Distribution.TestSuite.QuickCheck

-- * Tests

tests :: IO [Test]
tests = return $ map (uncurry comparisonTest) [
            (dumb, dietzSleatorAmortizedLogWithSize14)
        ]

-- * Order computations

newtype OrderComp = OrderComp [OrderStmt]

initialID :: Int
initialID = 1

instance Show OrderComp where

    show (OrderComp stmts)
        | null stmts = "no statements"
        | otherwise  = str ++ concatMap (", " ++) strs where

            str : strs = zipWith showStmt stmts nextIds

            newElemCounts = map newElemCount stmts

            nextIds = scanl (+) initialID newElemCounts

data CompGenState = CompGenState (Set Int) Int

instance Arbitrary OrderComp where

    arbitrary = sized $ \ size -> do
                    len <- choose (0, size)
                    stmts <- evalStateT (replicateM len genStmt)
                                        (CompGenState Set.empty initialID)
                    return (OrderComp stmts)

    shrink (OrderComp stmts) = if null stmts
                                   then []
                                   else [OrderComp (init stmts)]

type ComparisonMatrix = Map (Int, Int) Ordering

runComp :: Algorithm -> OrderComp -> ComparisonMatrix
runComp alg comp = compMatrix where

    compMatrix = withRawAlgorithm alg (\ rawAlg -> runST $ execComp rawAlg comp)

data CompExecState s e = CompExecState (ElementMap s e) Int

type ElementMap s e = Map Int (RawElement s e)

execComp :: RawAlgorithm s o e -> OrderComp -> ST s ComparisonMatrix
execComp rawAlg (OrderComp stmts) = do
    rawOrder <- newOrder rawAlg
    let execStmts = mapM_ (execStmt rawAlg rawOrder) stmts
    let initState = CompExecState Map.empty initialID
    ((), CompExecState elemMap _) <- runStateT execStmts initState
    let idElemPairs = Map.toList elemMap
    let comparisonPair (id1, elem1) (id2, elem2) = do
            ordering <- compareElements rawAlg elem1 elem2 rawOrder
            return ((id1, id2), ordering)
    comparisonPairs <- sequence $ liftM2 comparisonPair idElemPairs idElemPairs
    return $ Map.fromList comparisonPairs

data OrderStmt = NewMinimum
               | NewMaximum
               | NewAfter Int
               | NewBefore Int
               | Delete Int

newElemCount :: OrderStmt -> Int
newElemCount NewMinimum    = 1
newElemCount NewMaximum    = 1
newElemCount (NewAfter _)  = 1
newElemCount (NewBefore _) = 1
newElemCount (Delete _)    = 0

showStmt :: OrderStmt -> Int -> String
showStmt NewMinimum     = showNewStmt "newMinimum"
showStmt NewMaximum     = showNewStmt "newMaximum"
showStmt (NewAfter id)  = showNewStmt ("newAfter " ++ showElem id)
showStmt (NewBefore id) = showNewStmt ("newBefore " ++ showElem id)
showStmt (Delete id)    = const ("delete " ++ showElem id)

showNewStmt :: String -> Int -> String
showNewStmt base nextId = base ++ " -> " ++ showElem nextId

showElem :: Int -> String
showElem id = "x_" ++ show id

genStmt :: StateT CompGenState Gen OrderStmt
genStmt = do
    CompGenState liveIds nextId <- get
    let liveIdGen = elements (Set.toList liveIds)
    stmt <- lift $
            if Set.null liveIds
                then elements [NewMinimum, NewMaximum]
                else frequency [
                         (1, return NewMinimum),
                         (1, return NewMaximum),
                         (3, NewAfter <$> liveIdGen),
                         (3, NewBefore <$> liveIdGen),
                         (2, Delete <$> liveIdGen)
                     ]
    let newStmtIds = (Set.singleton nextId, Set.empty)
    let (newIds, deadIds) = case stmt of
                                NewMinimum  -> newStmtIds
                                NewMaximum  -> newStmtIds
                                NewAfter _  -> newStmtIds
                                NewBefore _ -> newStmtIds
                                Delete id   -> (Set.empty, Set.singleton id)
    put $ CompGenState ((liveIds `Set.union` newIds) `Set.difference` deadIds)
                       (nextId + Set.size newIds)
    return stmt

execStmt :: RawAlgorithm s o e
         -> RawOrder s o
         -> OrderStmt
         -> StateT (CompExecState s e) (ST s) ()
execStmt rawAlg rawOrder = exec where

    exec NewMinimum     = execNew newMinimum
    exec NewMaximum     = execNew newMaximum
    exec (NewAfter id)  = execNewNeighbor newAfter id
    exec (NewBefore id) = execNewNeighbor newBefore id
    exec (Delete id)    = execDelete id

    execNew new = do
        CompExecState elemMap nextId <- get
        rawElem <- lift $ new rawAlg rawOrder
        put $ CompExecState (Map.insert nextId rawElem elemMap) (succ nextId)

    execNewNeighbor newNeighbor id = do
        CompExecState elemMap _ <- get
        let new rawAlg = newNeighbor rawAlg (elemMap Map.! id)
        execNew new

    execDelete id = do
        CompExecState elemMap nextId <- get
        lift $ delete rawAlg (elemMap Map.! id) rawOrder
        put $ CompExecState (Map.delete id elemMap) nextId

-- * Named algorithms

data NamedAlgorithm = NamedAlgorithm String Algorithm

dumb :: NamedAlgorithm
dumb = NamedAlgorithm "Dumb" Algorithm.dumb

dietzSleatorAmortizedLogWithSize14 :: NamedAlgorithm
dietzSleatorAmortizedLogWithSize14 = NamedAlgorithm name alg where

    name = "Dietz and Sleator O(log n) amortized time"

    alg = Algorithm.dietzSleatorAmortizedLogWithSize 14

-- * Test pattern

comparisonTest :: NamedAlgorithm -> NamedAlgorithm -> Test
comparisonTest (NamedAlgorithm name1 alg1)
               (NamedAlgorithm name2 alg2) = testProperty name prop where

    name = name1 ++ " vs. " ++ name2

    prop comp = runComp alg1 comp == runComp alg2 comp