packages feed

hedgehog-utils-0.1.0.0: src/Hedgehog/Utils/Gen.hs

{-# LANGUAGE OverloadedStrings #-}

module Hedgehog.Utils.Gen
(
  -- * Generators
  boolSeq
, nat
, str
, elementFrequency

  -- * Utilities
, includeSize
, includeSize'
, shrinkFirst
, trace

-- * "Hedgehog.Gen" re-export
, module Hedgehog.Gen
)
where

import Hedgehog.Utils.Internal

import Hedgehog
import Hedgehog.Gen -- for re-export
import Hedgehog.Gen   qualified as Gen
import Hedgehog.Range qualified as Range

import Hedgehog.Internal.Gen  qualified as I_Gen
import Hedgehog.Internal.Tree qualified as Tree

import Data.Sequence qualified as Seq
import Debug.Trace   qualified as Debug
import GHC.Stack


boolSeq :: MonadGen m => Int -> m (Seq.Seq Bool)
boolSeq n = Gen.seq (Range.linear 0 n) Gen.bool

nat :: MonadGen m => m Int
nat = Gen.int (Range.linear 0 100)

str :: MonadGen m => m String
str = Gen.string (Range.linear 0 4) Gen.alpha


{- | Uses a weighted distribution to randomly select one of the elements in the list.

The generator shrinks towards the first element in the list.

/The input list must be non-empty./
-}
elementFrequency :: (MonadGen m, HasCallStack) => [(Int,a)] -> m a
elementFrequency xs =
  withFrozenCallStack $
  Gen.frequency [(w,Gen.constant a) | (w,a) <- xs]


includeSize :: MonadGen m => m a -> m (Size, a)
includeSize gen = Gen.sized $ \sz -> (sz,)<$>gen


includeSize' :: MonadGen m => m a -> m (String,a)
includeSize' = fmap f . includeSize
  where
    f (Size i,x) = (padLeft ' ' 3 (show i),x)


-- | Make a generator print any generated values using 'Debug.Trace.trace'. A custom string is prepended to each trace message.
trace :: (MonadGen m, Show a) => String -> m a -> m a
trace desc g = Gen.sized $ \(Size _sz) -> do
  x <- g
  Debug.trace (desc++" "++show x) (pure x)


{- | Give the generator additional shrinking options, while keeping the
  existing shrinks intact.

The new shrinks will be tried before existing shrinks. This is different from 'Gen.shrink', which is otherwise similar.
-}
shrinkFirst :: MonadGen m => (a -> [a]) -> m a -> m a
shrinkFirst f = withGenT $ I_Gen.mapGenT (expand' f)
  where

    -- "Expand a tree using an unfolding function."
    -- (adjusted version of expand' from Hedgehog.Internal.Tree)
    expand' :: Monad m => (a -> [a]) -> Tree.TreeT m a -> Tree.TreeT m a
    expand' f' m =
      Tree.TreeT $ do
        Tree.NodeT x xs <- Tree.runTreeT m
        pure . Tree.NodeT x $
          Tree.unfoldForest f' x ++ fmap (expand' f') xs
          -- orig.: fmap (expand' f) xs ++ Tree.unfoldForest f x

    withGenT g = fromGenT . g . toGenT