Skip to content

Commit

Permalink
test(journal): Add Model test for metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
symbiont-daniel-gustafsson committed Jan 21, 2022
1 parent 2c4a760 commit b6156fa
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/journal/journal.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ test-suite test
Journal.Internal.AtomicsTest
Journal.Internal.ByteBufferTest
Journal.Internal.MmapTest
Journal.Internal.MetricTest
JournalTest
TastyDiscover

Expand All @@ -101,6 +102,7 @@ test-suite test
, base
, binary
, bytestring
, containers
, directory
, HUnit
, journal
Expand Down
122 changes: 122 additions & 0 deletions src/journal/test/Journal/Internal/MetricTest.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
module Journal.Internal.MetricTest where

import Control.Monad (forM_, unless)
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import System.Directory
(canonicalizePath, getTemporaryDirectory, removePathForcibly)
import System.IO (hClose, openTempFile)
import Test.QuickCheck
import Test.QuickCheck.Monadic

import Journal.Internal.Metrics
import Journal.Internal.Utils hiding (assert)

-- can we generate this type?
data Counter = CA | CB | CC
deriving (Enum, Bounded, Show, Eq, Ord)

data Histogram = Histogram
deriving (Enum, Bounded)

schema :: MetricsSchema Counter Histogram
schema = MetricsSchema 1

genCounter :: Gen Counter
genCounter = elements [minBound .. maxBound]

data Command
= IncrCounter Counter Int
| ReadCounter Counter
deriving (Show, Eq)

constructorString :: Command -> String
constructorString (IncrCounter c _) = "IncrCounter " <> show c
constructorString c@(ReadCounter{}) = show c

data Model = Model
{ mCounters :: Map Counter Int
} deriving Show

newModel :: Model
newModel = Model
{ mCounters = Map.fromList [(c, 0) | c <- [minBound .. maxBound]]}

precondition :: Model -> Command -> Bool
precondition _ _ = True

data Response = Unit () | Int Int
deriving (Eq, Show)

prettyResponse :: Response -> String
prettyResponse = show

step :: Command -> Model -> (Model, Response)
step (IncrCounter c v) m = ( m { mCounters = Map.update (Just . (+ v)) c (mCounters m)}, Unit ())
step (ReadCounter c) m = (m, Int $ Maybe.fromJust $ Map.lookup c (mCounters m))

validProgram :: Model -> [Command] -> Bool
validProgram = go True
where
go False _m _cmds = False
go valid _m [] = valid
go valid m (cmd : cmds) = go (precondition m cmd) (fst (step cmd m)) cmds

genCommand :: Model -> Gen Command
genCommand _ = frequency
[ (,) 1 $ IncrCounter <$> genCounter <*> arbitrary
, (,) 1 $ ReadCounter <$> genCounter
]

genCommands :: Model -> Gen [Command]
genCommands m0 = sized (go m0)
where
go :: Model -> Int -> Gen [Command]
go _m 0 = return []
go m n = do
cmd <- genCommand m `suchThat` precondition m
cmds <- go (fst (step cmd m)) (n - 1)
return (cmd : cmds)

shrinkCommand :: Model -> Command -> [Command]
shrinkCommand _ _ = []

shrinkProgram :: Model -> [Command] -> [[Command]]
shrinkProgram model = filter (validProgram model) . shrinkList (shrinkCommand model)

exec :: Command -> Metrics Counter Histogram -> IO Response
exec (IncrCounter c v) metrics =
Unit <$> incrCounter metrics c v
exec (ReadCounter c) metrics =
Int <$> getCounter metrics c


-- we should maybe generalise this Property code, it similar to ByteBufferTest and JournalTest
prop_counter :: Property
prop_counter = do
let model = newModel
forAllShrink (genCommands model) (shrinkProgram model) $ \cmds -> monadicIO $ do
run (putStrLn ("Generated commands: " <> show cmds))
tmp <- run (canonicalizePath =<< getTemporaryDirectory)
(fp, h) <- run (openTempFile tmp "MetricsTest")
run (print fp)
run (fallocate fp $ metricSize schema)
metrics <- run (newMetrics schema fp)
run (hClose h)
monitor (tabulate "Commands" (map constructorString cmds))
go model metrics cmds
where
go _ _ [] = return True
go model metrics (cmd : cmds) = do
let (model', resp) = step cmd model
resp' <- run $ exec cmd metrics
assertWithFail (resp == resp') $
"expected: " ++ prettyResponse resp ++ "\n got: " ++ prettyResponse resp'
go model' metrics cmds

assertWithFail :: Monad m => Bool -> String -> PropertyM m ()
assertWithFail condition msg = do
unless condition $
monitor (counterexample ("Failed, " ++ msg))
assert condition

0 comments on commit b6156fa

Please sign in to comment.