advancedtelematic / quickcheck-state-machine

Test monadic programs using state machine based models

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Less boilerplate when defining generators

stevana opened this issue · comments

In Erlang QuickCheck generation is specified in two parts, first a weight function that assigns a frequency to a constructor given a model:

weight :: model Symbolic -> String -> Int

Erlang uses an atom for the constructor, above we simply use a string (perhaps this can be improved).

The second part is that we specify how to generate all arguments to an action. Let's introduce a concrete example:

data Action v :: * -> * where
  Create :: Int -> Action v Ref
  Use     :: Reference v Ref -> String -> Action v ()

type Model v = [(Reference v Ref, String)]

Create has an integer as argument and Use has a Reference v Ref and a string. So the specification would be something like:

genCreateArgs :: Model Symbolic -> Gen Int
genCreateArgs _ = arbitrary

genUseArgs :: Model Symbolic -> (Gen (Referenve v Ref), Gen String)
genUseArgs m = (elements (map fst m), arbtrary)

The two are then put together to create:

gen :: Model Symbolic -> Gen (Untyped Action)
gen m = frequency
  [ (weight m "Create", Untyped <$> (Create <$> genCreateArgs m))
  , (weight m "Use"   , Untyped <$> (uncurry Use <$> genUseArgs m))
  ]

This last part can be template Haskelled away using the first two parts.

Thoughts?

Here's another idea.

User provides:

weight :: model Symbolic -> act Symbolic resp -> Int

and:

generators :: model Symbolic -> [Gen (Untyped act)]

In the library we can rescale the generators using the weight as follows (weight and preconditions should be parameters to these functions):

gweight :: model Symbolic -> Gen (Untyped act) -> Gen (Int, Gen (Untyped act))
gweight m gen = do
  Untyped act <- gen
  return (if preconditions m act then weight m act else 0, gen)

genHelper :: model Symbolic -> [Gen (Untyped act)] 
          -> Gen (Untyped act)
genHelper m gs = frequency =<< sequence (map (gweight m) gs)

So the old interface can be implemented as follows:

generator :: model Symbolic -> Gen (Untyped act)
generator m = genHelper m generators

We had this implemented in the early commits of #209, but decided to go for the (still unpolished) transition matrix approach instead.