hedgehog 0.3 → 0.4
raw patch · 15 files changed
+2524/−1747 lines, 15 filesdep ~transformersPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: transformers
API changes (from Hackage documentation)
- Hedgehog.Gen: Gen :: (Size -> Seed -> Tree (MaybeT m) a) -> Gen m a
- Hedgehog.Gen: S :: Nat -> Nat
- Hedgehog.Gen: Z :: Nat
- Hedgehog.Gen: [:.] :: a -> Vec n a -> Vec (S n) a
- Hedgehog.Gen: [Nil] :: Vec Z a
- Hedgehog.Gen: [unGen] :: Gen m a -> Size -> Seed -> Tree (MaybeT m) a
- Hedgehog.Gen: atLeast :: Int -> [a] -> Bool
- Hedgehog.Gen: data Nat
- Hedgehog.Gen: data Vec n a
- Hedgehog.Gen: freeze :: Monad m => Gen m a -> Gen m (a, Gen m a)
- Hedgehog.Gen: generate :: Monad m => (Size -> Seed -> a) -> Gen m a
- Hedgehog.Gen: golden :: Size -> Size
- Hedgehog.Gen: instance Control.Monad.Base.MonadBase b m => Control.Monad.Base.MonadBase b (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Control.Monad.Error.Class.MonadError e m => Control.Monad.Error.Class.MonadError e (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Control.Monad.Morph.MFunctor Hedgehog.Gen.Gen
- Hedgehog.Gen: instance Control.Monad.Morph.MMonad Hedgehog.Gen.Gen
- Hedgehog.Gen: instance Control.Monad.Primitive.PrimMonad m => Control.Monad.Primitive.PrimMonad (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Control.Monad.Reader.Class.MonadReader r m => Control.Monad.Reader.Class.MonadReader r (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Control.Monad.Trans.Class.MonadTrans Hedgehog.Gen.Gen
- Hedgehog.Gen: instance Control.Monad.Trans.Resource.Internal.MonadResource m => Control.Monad.Trans.Resource.Internal.MonadResource (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Control.Monad.Writer.Class.MonadWriter w m => Control.Monad.Writer.Class.MonadWriter w (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Data.Foldable.Foldable (Hedgehog.Gen.Subterms n)
- Hedgehog.Gen: instance Data.Foldable.Foldable (Hedgehog.Gen.Vec n)
- Hedgehog.Gen: instance Data.Traversable.Traversable (Hedgehog.Gen.Subterms n)
- Hedgehog.Gen: instance Data.Traversable.Traversable (Hedgehog.Gen.Vec n)
- Hedgehog.Gen: instance GHC.Base.Functor (Hedgehog.Gen.Subterms n)
- Hedgehog.Gen: instance GHC.Base.Functor (Hedgehog.Gen.Vec n)
- Hedgehog.Gen: instance GHC.Base.Functor m => GHC.Base.Functor (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance GHC.Base.Monad m => GHC.Base.Alternative (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance GHC.Base.Monad m => GHC.Base.Applicative (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance GHC.Base.Monad m => GHC.Base.Monad (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance GHC.Base.Monad m => GHC.Base.MonadPlus (Hedgehog.Gen.Gen m)
- Hedgehog.Gen: instance Hedgehog.Internal.Distributive.Distributive Hedgehog.Gen.Gen
- Hedgehog.Gen: isSurrogate :: Char -> Bool
- Hedgehog.Gen: liftTree :: Tree (MaybeT m) a -> Gen m a
- Hedgehog.Gen: mapGen :: (Tree (MaybeT m) a -> Tree (MaybeT n) b) -> Gen m a -> Gen n b
- Hedgehog.Gen: newtype Gen m a
- Hedgehog.Gen: renderNodes :: (Monad m, Show a) => Size -> Seed -> Gen m a -> Tree m String
- Hedgehog.Gen: runDiscardEffect :: Monad m => Tree (MaybeT m) a -> Tree m (Maybe a)
- Hedgehog.Gen: runGen :: Size -> Seed -> Gen m a -> Tree (MaybeT m) a
- Hedgehog.Gen: subtermMVec :: Monad m => Vec n (Gen m a) -> (Vec n a -> Gen m a) -> Gen m a
- Hedgehog.Range: Range :: !a -> (Size -> (a, a)) -> Range a
- Hedgehog.Range: clamp :: Ord a => a -> a -> a -> a
- Hedgehog.Range: instance GHC.Base.Functor Hedgehog.Range.Range
- Hedgehog.Range: instance GHC.Classes.Eq Hedgehog.Range.Size
- Hedgehog.Range: instance GHC.Classes.Ord Hedgehog.Range.Size
- Hedgehog.Range: instance GHC.Enum.Enum Hedgehog.Range.Size
- Hedgehog.Range: instance GHC.Num.Num Hedgehog.Range.Size
- Hedgehog.Range: instance GHC.Read.Read Hedgehog.Range.Size
- Hedgehog.Range: instance GHC.Real.Integral Hedgehog.Range.Size
- Hedgehog.Range: instance GHC.Real.Real Hedgehog.Range.Size
- Hedgehog.Range: instance GHC.Show.Show Hedgehog.Range.Size
- Hedgehog.Range: scaleExponential :: Integral a => Size -> a -> a -> a
- Hedgehog.Range: scaleExponentialFloat :: Floating a => Size -> a -> a -> a
- Hedgehog.Range: scaleLinear :: Integral a => Size -> a -> a -> a
- Hedgehog.Range: scaleLinearFrac :: Fractional a => Size -> a -> a -> a
+ Hedgehog: Command :: (state Symbolic -> Maybe (Gen n (input Symbolic))) -> (input Concrete -> Test m output) -> [Callback input output m state] -> Command n m
+ Hedgehog: Ensure :: (state Concrete -> input Concrete -> output -> Test m ()) -> Callback input output m state
+ Hedgehog: Opaque :: a -> Opaque a
+ Hedgehog: Require :: (state Symbolic -> input Symbolic -> Bool) -> Callback input output m state
+ Hedgehog: Update :: (forall v. Ord1 v => state v -> input v -> v output -> state v) -> Callback input output m state
+ Hedgehog: [Concrete] :: a -> Concrete a
+ Hedgehog: [Symbolic] :: Typeable a => Var -> Symbolic a
+ Hedgehog: [commandCallbacks] :: Command n m -> [Callback input output m state]
+ Hedgehog: [commandExecute] :: Command n m -> input Concrete -> Test m output
+ Hedgehog: [commandGen] :: Command n m -> state Symbolic -> Maybe (Gen n (input Symbolic))
+ Hedgehog: [unOpaque] :: Opaque a -> a
+ Hedgehog: class Eq1 (f :: * -> *)
+ Hedgehog: class HTraversable t
+ Hedgehog: class Eq1 f => Ord1 (f :: * -> *)
+ Hedgehog: class Show1 (f :: * -> *)
+ Hedgehog: compare1 :: (Ord1 f, Ord a) => f a -> f a -> Ordering
+ Hedgehog: data Action m (state :: (* -> *) -> *)
+ Hedgehog: data Callback input output m state
+ Hedgehog: data Command n m (state :: (* -> *) -> *)
+ Hedgehog: data Symbolic a
+ Hedgehog: data Var
+ Hedgehog: eq1 :: (Eq1 f, Eq a) => f a -> f a -> Bool
+ Hedgehog: executeSequential :: forall m state. (HasCallStack, MonadCatch m) => (forall v. state v) -> [Action m state] -> Test m ()
+ Hedgehog: htraverse :: (HTraversable t, Applicative f) => (forall a. g a -> f (h a)) -> t g -> f (t h)
+ Hedgehog: liftCatch :: (MonadCatch m, HasCallStack) => m a -> Test m a
+ Hedgehog: liftCatchIO :: (MonadIO m, HasCallStack) => IO a -> Test m a
+ Hedgehog: newtype Concrete a
+ Hedgehog: newtype Opaque a
+ Hedgehog: showsPrec1 :: (Show1 f, Show a) => Int -> f a -> ShowS
+ Hedgehog: withCatch :: (MonadCatch m, HasCallStack) => Test m a -> Test m a
+ Hedgehog.Gen: actions :: (Monad n, Monad m) => Range Int -> (forall v. state v) -> [Command n m state] -> Gen n [Action m state]
+ Hedgehog.Gen: data Gen m a
+ Hedgehog.Internal.Exception: TypedException :: SomeException -> TypedException
+ Hedgehog.Internal.Exception: instance GHC.Show.Show Hedgehog.Internal.Exception.TypedException
+ Hedgehog.Internal.Exception: newtype TypedException
+ Hedgehog.Internal.Exception: tryAll :: MonadCatch m => m a -> m (Either TypedException a)
+ Hedgehog.Internal.Gen: Gen :: (Size -> Seed -> Tree (MaybeT m) a) -> Gen m a
+ Hedgehog.Internal.Gen: S :: Nat -> Nat
+ Hedgehog.Internal.Gen: Z :: Nat
+ Hedgehog.Internal.Gen: [:.] :: a -> Vec n a -> Vec (S n) a
+ Hedgehog.Internal.Gen: [Nil] :: Vec Z a
+ Hedgehog.Internal.Gen: [unGen] :: Gen m a -> Size -> Seed -> Tree (MaybeT m) a
+ Hedgehog.Internal.Gen: alpha :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: alphaNum :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: ascii :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: atLeast :: Int -> [a] -> Bool
+ Hedgehog.Internal.Gen: binit :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: bool :: Monad m => Gen m Bool
+ Hedgehog.Internal.Gen: bool_ :: Monad m => Gen m Bool
+ Hedgehog.Internal.Gen: bytes :: Monad m => Range Int -> Gen m ByteString
+ Hedgehog.Internal.Gen: choice :: Monad m => [Gen m a] -> Gen m a
+ Hedgehog.Internal.Gen: constant :: Monad m => a -> Gen m a
+ Hedgehog.Internal.Gen: data Nat
+ Hedgehog.Internal.Gen: data Vec n a
+ Hedgehog.Internal.Gen: digit :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: discard :: Monad m => Gen m a
+ Hedgehog.Internal.Gen: double :: Monad m => Range Double -> Gen m Double
+ Hedgehog.Internal.Gen: element :: Monad m => [a] -> Gen m a
+ Hedgehog.Internal.Gen: enum :: (Monad m, Enum a) => a -> a -> Gen m a
+ Hedgehog.Internal.Gen: enumBounded :: (Monad m, Enum a, Bounded a) => Gen m a
+ Hedgehog.Internal.Gen: filter :: Monad m => (a -> Bool) -> Gen m a -> Gen m a
+ Hedgehog.Internal.Gen: float :: Monad m => Range Float -> Gen m Float
+ Hedgehog.Internal.Gen: freeze :: Monad m => Gen m a -> Gen m (a, Gen m a)
+ Hedgehog.Internal.Gen: frequency :: Monad m => [(Int, Gen m a)] -> Gen m a
+ Hedgehog.Internal.Gen: generate :: Monad m => (Size -> Seed -> a) -> Gen m a
+ Hedgehog.Internal.Gen: golden :: Size -> Size
+ Hedgehog.Internal.Gen: hexit :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: instance Control.Monad.Base.MonadBase b m => Control.Monad.Base.MonadBase b (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Control.Monad.Error.Class.MonadError e m => Control.Monad.Error.Class.MonadError e (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Control.Monad.Morph.MFunctor Hedgehog.Internal.Gen.Gen
+ Hedgehog.Internal.Gen: instance Control.Monad.Morph.MMonad Hedgehog.Internal.Gen.Gen
+ Hedgehog.Internal.Gen: instance Control.Monad.Primitive.PrimMonad m => Control.Monad.Primitive.PrimMonad (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Control.Monad.Reader.Class.MonadReader r m => Control.Monad.Reader.Class.MonadReader r (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Control.Monad.Trans.Class.MonadTrans Hedgehog.Internal.Gen.Gen
+ Hedgehog.Internal.Gen: instance Control.Monad.Trans.Resource.Internal.MonadResource m => Control.Monad.Trans.Resource.Internal.MonadResource (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Control.Monad.Writer.Class.MonadWriter w m => Control.Monad.Writer.Class.MonadWriter w (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Data.Foldable.Foldable (Hedgehog.Internal.Gen.Subterms n)
+ Hedgehog.Internal.Gen: instance Data.Foldable.Foldable (Hedgehog.Internal.Gen.Vec n)
+ Hedgehog.Internal.Gen: instance Data.Traversable.Traversable (Hedgehog.Internal.Gen.Subterms n)
+ Hedgehog.Internal.Gen: instance Data.Traversable.Traversable (Hedgehog.Internal.Gen.Vec n)
+ Hedgehog.Internal.Gen: instance GHC.Base.Functor (Hedgehog.Internal.Gen.Subterms n)
+ Hedgehog.Internal.Gen: instance GHC.Base.Functor (Hedgehog.Internal.Gen.Vec n)
+ Hedgehog.Internal.Gen: instance GHC.Base.Functor m => GHC.Base.Functor (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance GHC.Base.Monad m => GHC.Base.Alternative (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance GHC.Base.Monad m => GHC.Base.Applicative (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance GHC.Base.Monad m => GHC.Base.Monad (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance GHC.Base.Monad m => GHC.Base.MonadPlus (Hedgehog.Internal.Gen.Gen m)
+ Hedgehog.Internal.Gen: instance Hedgehog.Internal.Distributive.Distributive Hedgehog.Internal.Gen.Gen
+ Hedgehog.Internal.Gen: int :: Monad m => Range Int -> Gen m Int
+ Hedgehog.Internal.Gen: int16 :: Monad m => Range Int16 -> Gen m Int16
+ Hedgehog.Internal.Gen: int32 :: Monad m => Range Int32 -> Gen m Int32
+ Hedgehog.Internal.Gen: int64 :: Monad m => Range Int64 -> Gen m Int64
+ Hedgehog.Internal.Gen: int8 :: Monad m => Range Int8 -> Gen m Int8
+ Hedgehog.Internal.Gen: integral :: (Monad m, Integral a) => Range a -> Gen m a
+ Hedgehog.Internal.Gen: integral_ :: (Monad m, Integral a) => Range a -> Gen m a
+ Hedgehog.Internal.Gen: isSurrogate :: Char -> Bool
+ Hedgehog.Internal.Gen: just :: Monad m => Gen m (Maybe a) -> Gen m a
+ Hedgehog.Internal.Gen: latin1 :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: liftTree :: Tree (MaybeT m) a -> Gen m a
+ Hedgehog.Internal.Gen: list :: Monad m => Range Int -> Gen m a -> Gen m [a]
+ Hedgehog.Internal.Gen: lower :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: map :: (Monad m, Ord k) => Range Int -> Gen m (k, v) -> Gen m (Map k v)
+ Hedgehog.Internal.Gen: mapGen :: (Tree (MaybeT m) a -> Tree (MaybeT n) b) -> Gen m a -> Gen n b
+ Hedgehog.Internal.Gen: maybe :: Monad m => Gen m a -> Gen m (Maybe a)
+ Hedgehog.Internal.Gen: newtype Gen m a
+ Hedgehog.Internal.Gen: nonEmpty :: Monad m => Range Int -> Gen m a -> Gen m (NonEmpty a)
+ Hedgehog.Internal.Gen: octit :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: print :: (MonadIO m, Show a) => Gen m a -> m ()
+ Hedgehog.Internal.Gen: printTree :: (MonadIO m, Show a) => Gen m a -> m ()
+ Hedgehog.Internal.Gen: printTreeWith :: (MonadIO m, Show a) => Size -> Seed -> Gen m a -> m ()
+ Hedgehog.Internal.Gen: printWith :: (MonadIO m, Show a) => Size -> Seed -> Gen m a -> m ()
+ Hedgehog.Internal.Gen: prune :: Monad m => Gen m a -> Gen m a
+ Hedgehog.Internal.Gen: realFloat :: (Monad m, RealFloat a) => Range a -> Gen m a
+ Hedgehog.Internal.Gen: realFrac_ :: (Monad m, RealFrac a) => Range a -> Gen m a
+ Hedgehog.Internal.Gen: recursive :: ([Gen m a] -> Gen m a) -> [Gen m a] -> [Gen m a] -> Gen m a
+ Hedgehog.Internal.Gen: renderNodes :: (Monad m, Show a) => Size -> Seed -> Gen m a -> Tree m String
+ Hedgehog.Internal.Gen: resize :: Size -> Gen m a -> Gen m a
+ Hedgehog.Internal.Gen: runDiscardEffect :: Monad m => Tree (MaybeT m) a -> Tree m (Maybe a)
+ Hedgehog.Internal.Gen: runGen :: Size -> Seed -> Gen m a -> Tree (MaybeT m) a
+ Hedgehog.Internal.Gen: sample :: MonadIO m => Gen m a -> m [a]
+ Hedgehog.Internal.Gen: scale :: (Size -> Size) -> Gen m a -> Gen m a
+ Hedgehog.Internal.Gen: seq :: Monad m => Range Int -> Gen m a -> Gen m (Seq a)
+ Hedgehog.Internal.Gen: set :: (Monad m, Ord a) => Range Int -> Gen m a -> Gen m (Set a)
+ Hedgehog.Internal.Gen: shrink :: Monad m => (a -> [a]) -> Gen m a -> Gen m a
+ Hedgehog.Internal.Gen: shuffle :: Monad m => [a] -> Gen m [a]
+ Hedgehog.Internal.Gen: sized :: (Size -> Gen m a) -> Gen m a
+ Hedgehog.Internal.Gen: small :: Gen m a -> Gen m a
+ Hedgehog.Internal.Gen: string :: Monad m => Range Int -> Gen m Char -> Gen m String
+ Hedgehog.Internal.Gen: subsequence :: Monad m => [a] -> Gen m [a]
+ Hedgehog.Internal.Gen: subterm :: Monad m => Gen m a -> (a -> a) -> Gen m a
+ Hedgehog.Internal.Gen: subterm2 :: Monad m => Gen m a -> Gen m a -> (a -> a -> a) -> Gen m a
+ Hedgehog.Internal.Gen: subterm3 :: Monad m => Gen m a -> Gen m a -> Gen m a -> (a -> a -> a -> a) -> Gen m a
+ Hedgehog.Internal.Gen: subtermM :: Monad m => Gen m a -> (a -> Gen m a) -> Gen m a
+ Hedgehog.Internal.Gen: subtermM2 :: Monad m => Gen m a -> Gen m a -> (a -> a -> Gen m a) -> Gen m a
+ Hedgehog.Internal.Gen: subtermM3 :: Monad m => Gen m a -> Gen m a -> Gen m a -> (a -> a -> a -> Gen m a) -> Gen m a
+ Hedgehog.Internal.Gen: subtermMVec :: Monad m => Vec n (Gen m a) -> (Vec n a -> Gen m a) -> Gen m a
+ Hedgehog.Internal.Gen: text :: Monad m => Range Int -> Gen m Char -> Gen m Text
+ Hedgehog.Internal.Gen: unicode :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: unicodeAll :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: upper :: Monad m => Gen m Char
+ Hedgehog.Internal.Gen: utf8 :: Monad m => Range Int -> Gen m Char -> Gen m ByteString
+ Hedgehog.Internal.Gen: word :: Monad m => Range Word -> Gen m Word
+ Hedgehog.Internal.Gen: word16 :: Monad m => Range Word16 -> Gen m Word16
+ Hedgehog.Internal.Gen: word32 :: Monad m => Range Word32 -> Gen m Word32
+ Hedgehog.Internal.Gen: word64 :: Monad m => Range Word64 -> Gen m Word64
+ Hedgehog.Internal.Gen: word8 :: Monad m => Range Word8 -> Gen m Word8
+ Hedgehog.Internal.HTraversable: class HTraversable t
+ Hedgehog.Internal.HTraversable: htraverse :: (HTraversable t, Applicative f) => (forall a. g a -> f (h a)) -> t g -> f (t h)
+ Hedgehog.Internal.Opaque: Opaque :: a -> Opaque a
+ Hedgehog.Internal.Opaque: [unOpaque] :: Opaque a -> a
+ Hedgehog.Internal.Opaque: instance GHC.Classes.Eq a => GHC.Classes.Eq (Hedgehog.Internal.Opaque.Opaque a)
+ Hedgehog.Internal.Opaque: instance GHC.Classes.Ord a => GHC.Classes.Ord (Hedgehog.Internal.Opaque.Opaque a)
+ Hedgehog.Internal.Opaque: instance GHC.Show.Show (Hedgehog.Internal.Opaque.Opaque a)
+ Hedgehog.Internal.Opaque: newtype Opaque a
+ Hedgehog.Internal.Property: liftCatch :: (MonadCatch m, HasCallStack) => m a -> Test m a
+ Hedgehog.Internal.Property: liftCatchIO :: (MonadIO m, HasCallStack) => IO a -> Test m a
+ Hedgehog.Internal.Property: withCatch :: (MonadCatch m, HasCallStack) => Test m a -> Test m a
+ Hedgehog.Internal.Range: Range :: !a -> (Size -> (a, a)) -> Range a
+ Hedgehog.Internal.Range: Size :: Int -> Size
+ Hedgehog.Internal.Range: [unSize] :: Size -> Int
+ Hedgehog.Internal.Range: bounds :: Size -> Range a -> (a, a)
+ Hedgehog.Internal.Range: clamp :: Ord a => a -> a -> a -> a
+ Hedgehog.Internal.Range: constant :: a -> a -> Range a
+ Hedgehog.Internal.Range: constantBounded :: (Bounded a, Num a) => Range a
+ Hedgehog.Internal.Range: constantFrom :: a -> a -> a -> Range a
+ Hedgehog.Internal.Range: data Range a
+ Hedgehog.Internal.Range: exponential :: Integral a => a -> a -> Range a
+ Hedgehog.Internal.Range: exponentialBounded :: (Bounded a, Integral a) => Range a
+ Hedgehog.Internal.Range: exponentialFloat :: (Floating a, Ord a) => a -> a -> Range a
+ Hedgehog.Internal.Range: exponentialFloatFrom :: (Floating a, Ord a) => a -> a -> a -> Range a
+ Hedgehog.Internal.Range: exponentialFrom :: Integral a => a -> a -> a -> Range a
+ Hedgehog.Internal.Range: instance GHC.Base.Functor Hedgehog.Internal.Range.Range
+ Hedgehog.Internal.Range: instance GHC.Classes.Eq Hedgehog.Internal.Range.Size
+ Hedgehog.Internal.Range: instance GHC.Classes.Ord Hedgehog.Internal.Range.Size
+ Hedgehog.Internal.Range: instance GHC.Enum.Enum Hedgehog.Internal.Range.Size
+ Hedgehog.Internal.Range: instance GHC.Num.Num Hedgehog.Internal.Range.Size
+ Hedgehog.Internal.Range: instance GHC.Read.Read Hedgehog.Internal.Range.Size
+ Hedgehog.Internal.Range: instance GHC.Real.Integral Hedgehog.Internal.Range.Size
+ Hedgehog.Internal.Range: instance GHC.Real.Real Hedgehog.Internal.Range.Size
+ Hedgehog.Internal.Range: instance GHC.Show.Show Hedgehog.Internal.Range.Size
+ Hedgehog.Internal.Range: linear :: Integral a => a -> a -> Range a
+ Hedgehog.Internal.Range: linearBounded :: (Bounded a, Integral a) => Range a
+ Hedgehog.Internal.Range: linearFrac :: (Fractional a, Ord a) => a -> a -> Range a
+ Hedgehog.Internal.Range: linearFracFrom :: (Fractional a, Ord a) => a -> a -> a -> Range a
+ Hedgehog.Internal.Range: linearFrom :: Integral a => a -> a -> a -> Range a
+ Hedgehog.Internal.Range: lowerBound :: Ord a => Size -> Range a -> a
+ Hedgehog.Internal.Range: newtype Size
+ Hedgehog.Internal.Range: origin :: Range a -> a
+ Hedgehog.Internal.Range: scaleExponential :: Integral a => Size -> a -> a -> a
+ Hedgehog.Internal.Range: scaleExponentialFloat :: Floating a => Size -> a -> a -> a
+ Hedgehog.Internal.Range: scaleLinear :: Integral a => Size -> a -> a -> a
+ Hedgehog.Internal.Range: scaleLinearFrac :: Fractional a => Size -> a -> a -> a
+ Hedgehog.Internal.Range: singleton :: a -> Range a
+ Hedgehog.Internal.Range: upperBound :: Ord a => Size -> Range a -> a
+ Hedgehog.Internal.State: Action :: input Symbolic -> Symbolic output -> (input Concrete -> Test m output) -> (state Symbolic -> input Symbolic -> Bool) -> (forall v. Ord1 v => state v -> input v -> v output -> state v) -> (state Concrete -> input Concrete -> output -> Test m ()) -> Action m
+ Hedgehog.Internal.State: Command :: (state Symbolic -> Maybe (Gen n (input Symbolic))) -> (input Concrete -> Test m output) -> [Callback input output m state] -> Command n m
+ Hedgehog.Internal.State: Ensure :: (state Concrete -> input Concrete -> output -> Test m ()) -> Callback input output m state
+ Hedgehog.Internal.State: Environment :: Map Var Dynamic -> Environment
+ Hedgehog.Internal.State: EnvironmentTypeError :: !TypeRep -> !TypeRep -> EnvironmentError
+ Hedgehog.Internal.State: EnvironmentValueNotFound :: !Var -> EnvironmentError
+ Hedgehog.Internal.State: Require :: (state Symbolic -> input Symbolic -> Bool) -> Callback input output m state
+ Hedgehog.Internal.State: Update :: (forall v. Ord1 v => state v -> input v -> v output -> state v) -> Callback input output m state
+ Hedgehog.Internal.State: Var :: Int -> Var
+ Hedgehog.Internal.State: [Concrete] :: a -> Concrete a
+ Hedgehog.Internal.State: [Symbolic] :: Typeable a => Var -> Symbolic a
+ Hedgehog.Internal.State: [actionEnsure] :: Action m -> state Concrete -> input Concrete -> output -> Test m ()
+ Hedgehog.Internal.State: [actionExecute] :: Action m -> input Concrete -> Test m output
+ Hedgehog.Internal.State: [actionInput] :: Action m -> input Symbolic
+ Hedgehog.Internal.State: [actionOutput] :: Action m -> Symbolic output
+ Hedgehog.Internal.State: [actionRequire] :: Action m -> state Symbolic -> input Symbolic -> Bool
+ Hedgehog.Internal.State: [actionUpdate] :: Action m -> forall v. Ord1 v => state v -> input v -> v output -> state v
+ Hedgehog.Internal.State: [commandCallbacks] :: Command n m -> [Callback input output m state]
+ Hedgehog.Internal.State: [commandExecute] :: Command n m -> input Concrete -> Test m output
+ Hedgehog.Internal.State: [commandGen] :: Command n m -> state Symbolic -> Maybe (Gen n (input Symbolic))
+ Hedgehog.Internal.State: [unEnvironment] :: Environment -> Map Var Dynamic
+ Hedgehog.Internal.State: action :: (Monad n, Monad m) => [Command n m state] -> Gen (StateT (state Symbolic, Var) n) (Action m state)
+ Hedgehog.Internal.State: actions :: (Monad n, Monad m) => Range Int -> (forall v. state v) -> [Command n m state] -> Gen n [Action m state]
+ Hedgehog.Internal.State: commandGenOK :: Command n m state -> state Symbolic -> Bool
+ Hedgehog.Internal.State: data Action m (state :: (* -> *) -> *)
+ Hedgehog.Internal.State: data Callback input output m state
+ Hedgehog.Internal.State: data Command n m (state :: (* -> *) -> *)
+ Hedgehog.Internal.State: data EnvironmentError
+ Hedgehog.Internal.State: data Symbolic a
+ Hedgehog.Internal.State: dropInvalid :: (forall v. state v) -> [Action m state] -> [Action m state]
+ Hedgehog.Internal.State: emptyEnvironment :: Environment
+ Hedgehog.Internal.State: execute :: (HasCallStack, Monad m) => (state Concrete, Environment) -> Action m state -> Test m (state Concrete, Environment)
+ Hedgehog.Internal.State: executeSequential :: forall m state. (HasCallStack, MonadCatch m) => (forall v. state v) -> [Action m state] -> Test m ()
+ Hedgehog.Internal.State: insertConcrete :: Symbolic a -> Concrete a -> Environment -> Environment
+ Hedgehog.Internal.State: instance Data.Foldable.Foldable Hedgehog.Internal.State.Concrete
+ Hedgehog.Internal.State: instance Data.Functor.Classes.Eq1 Hedgehog.Internal.State.Concrete
+ Hedgehog.Internal.State: instance Data.Functor.Classes.Eq1 Hedgehog.Internal.State.Symbolic
+ Hedgehog.Internal.State: instance Data.Functor.Classes.Ord1 Hedgehog.Internal.State.Concrete
+ Hedgehog.Internal.State: instance Data.Functor.Classes.Ord1 Hedgehog.Internal.State.Symbolic
+ Hedgehog.Internal.State: instance Data.Functor.Classes.Show1 Hedgehog.Internal.State.Concrete
+ Hedgehog.Internal.State: instance Data.Functor.Classes.Show1 Hedgehog.Internal.State.Symbolic
+ Hedgehog.Internal.State: instance Data.Traversable.Traversable Hedgehog.Internal.State.Concrete
+ Hedgehog.Internal.State: instance GHC.Base.Functor Hedgehog.Internal.State.Concrete
+ Hedgehog.Internal.State: instance GHC.Classes.Eq (Hedgehog.Internal.State.Symbolic a)
+ Hedgehog.Internal.State: instance GHC.Classes.Eq Hedgehog.Internal.State.EnvironmentError
+ Hedgehog.Internal.State: instance GHC.Classes.Eq Hedgehog.Internal.State.Var
+ Hedgehog.Internal.State: instance GHC.Classes.Eq a => GHC.Classes.Eq (Hedgehog.Internal.State.Concrete a)
+ Hedgehog.Internal.State: instance GHC.Classes.Ord (Hedgehog.Internal.State.Symbolic a)
+ Hedgehog.Internal.State: instance GHC.Classes.Ord Hedgehog.Internal.State.EnvironmentError
+ Hedgehog.Internal.State: instance GHC.Classes.Ord Hedgehog.Internal.State.Var
+ Hedgehog.Internal.State: instance GHC.Classes.Ord a => GHC.Classes.Ord (Hedgehog.Internal.State.Concrete a)
+ Hedgehog.Internal.State: instance GHC.Num.Num Hedgehog.Internal.State.Var
+ Hedgehog.Internal.State: instance GHC.Show.Show (Hedgehog.Internal.State.Action m state)
+ Hedgehog.Internal.State: instance GHC.Show.Show (Hedgehog.Internal.State.Symbolic a)
+ Hedgehog.Internal.State: instance GHC.Show.Show Hedgehog.Internal.State.Environment
+ Hedgehog.Internal.State: instance GHC.Show.Show Hedgehog.Internal.State.EnvironmentError
+ Hedgehog.Internal.State: instance GHC.Show.Show Hedgehog.Internal.State.Var
+ Hedgehog.Internal.State: instance GHC.Show.Show a => GHC.Show.Show (Hedgehog.Internal.State.Concrete a)
+ Hedgehog.Internal.State: newtype Concrete a
+ Hedgehog.Internal.State: newtype Environment
+ Hedgehog.Internal.State: newtype Var
+ Hedgehog.Internal.State: reify :: HTraversable t => Environment -> t Symbolic -> Either EnvironmentError (t Concrete)
+ Hedgehog.Internal.State: reifyDynamic :: forall a. Typeable a => Dynamic -> Either EnvironmentError (Concrete a)
+ Hedgehog.Internal.State: reifyEnvironment :: Environment -> (forall a. Symbolic a -> Either EnvironmentError (Concrete a))
+ Hedgehog.Internal.State: takeVariables :: HTraversable t => t Symbolic -> Set Var
+ Hedgehog.Internal.State: variablesOK :: HTraversable t => t Symbolic -> Set Var -> Bool
Files
- CHANGELOG.md +5/−0
- README.md +3/−1
- hedgehog.cabal +8/−2
- src/Hedgehog.hs +41/−5
- src/Hedgehog/Gen.hs +110/−1304
- src/Hedgehog/Internal/Distributive.hs +2/−0
- src/Hedgehog/Internal/Exception.hs +42/−0
- src/Hedgehog/Internal/Gen.hs +1304/−0
- src/Hedgehog/Internal/HTraversable.hs +10/−0
- src/Hedgehog/Internal/Opaque.hs +25/−0
- src/Hedgehog/Internal/Property.hs +34/−2
- src/Hedgehog/Internal/Range.hs +461/−0
- src/Hedgehog/Internal/Runner.hs +3/−4
- src/Hedgehog/Internal/State.hs +474/−0
- src/Hedgehog/Range.hs +2/−429
CHANGELOG.md view
@@ -1,3 +1,8 @@+## Version 0.4 (2017-06-28)++- Abstract state machine testing, check out the [process registry example](https://github.com/hedgehogqa/haskell-hedgehog/blob/master/hedgehog-example/test/Test/Example/Registry.hs) to see how it works (#89, @jystic)+- `liftCatch`, `liftCatchIO`, `withCatch` functions for isolating exceptions during tests (#89, @jystic)+ ## Version 0.3 (2017-06-11) - Exponential range combinators (#43, @chris-martin)
README.md view
@@ -57,6 +57,8 @@ to run manually instead: ```hs+{-# LANGUAGE OverloadedStrings #-}+ tests :: IO Bool tests = checkParallel $ Group "Test.Example" [@@ -74,7 +76,7 @@ ``` [hackage]: http://hackage.haskell.org/package/hedgehog- [hackage-shield]: https://img.shields.io/badge/hackage-v0.3-blue.svg+ [hackage-shield]: https://img.shields.io/badge/hackage-v0.4-blue.svg [travis]: https://travis-ci.org/hedgehogqa/haskell-hedgehog [travis-shield]: https://travis-ci.org/hedgehogqa/haskell-hedgehog.svg?branch=master
hedgehog.cabal view
@@ -1,7 +1,7 @@ name: hedgehog version:- 0.3+ 0.4 license: BSD3 author:@@ -65,7 +65,7 @@ , text >= 1.1 && < 1.3 , th-lift >= 0.7 && < 0.8 , time >= 1.4 && < 1.9- , transformers >= 0.3 && < 0.6+ , transformers >= 0.5 && < 0.6 , transformers-base >= 0.4 && < 0.5 , wl-pprint-annotated >= 0.0 && < 0.2 @@ -91,8 +91,13 @@ Hedgehog.Internal.Config Hedgehog.Internal.Discovery Hedgehog.Internal.Distributive+ Hedgehog.Internal.Exception+ Hedgehog.Internal.Gen+ Hedgehog.Internal.HTraversable+ Hedgehog.Internal.Opaque Hedgehog.Internal.Property Hedgehog.Internal.Queue+ Hedgehog.Internal.Range Hedgehog.Internal.Region Hedgehog.Internal.Report Hedgehog.Internal.Runner@@ -100,6 +105,7 @@ Hedgehog.Internal.Show Hedgehog.Internal.Shrink Hedgehog.Internal.Source+ Hedgehog.Internal.State Hedgehog.Internal.TH Hedgehog.Internal.Tree Hedgehog.Internal.Tripping
src/Hedgehog.hs view
@@ -29,6 +29,8 @@ -- If you prefer to avoid macros, you can specify the group of properties to -- run manually instead: --+-- > {-# LANGUAGE OverloadedStrings #-}+-- > -- > tests :: IO Bool -- > tests = -- > checkParallel $ Group "Test.Example" [@@ -82,33 +84,67 @@ , assert , (===) + , liftCatch+ , liftCatchIO , liftEither , liftExceptT++ , withCatch , withExceptT , withResourceT , tripping + -- * Abstract State Machine+ , Command(..)+ , Callback(..)+ , Action+ , executeSequential++ , Concrete(..)+ , Symbolic(..)+ , Var+ , Opaque(..)+ -- * Transformers , distribute++ -- * Functors+ , HTraversable(..)++ , Eq1+ , eq1++ , Ord1+ , compare1++ , Show1+ , showsPrec1 ) where -import Hedgehog.Gen (Gen)-import Hedgehog.Internal.Distributive (distribute)+import Data.Functor.Classes (Eq1, eq1, Ord1, compare1, Show1, showsPrec1)++import Hedgehog.Internal.Distributive (Distributive(..))+import Hedgehog.Internal.Gen (Gen)+import Hedgehog.Internal.HTraversable (HTraversable(..))+import Hedgehog.Internal.Opaque (Opaque(..)) import Hedgehog.Internal.Property (annotate, annotateShow) import Hedgehog.Internal.Property (assert, (===)) import Hedgehog.Internal.Property (discard, failure, success) import Hedgehog.Internal.Property (DiscardLimit, withDiscards) import Hedgehog.Internal.Property (footnote, footnoteShow) import Hedgehog.Internal.Property (forAll, forAllWith)-import Hedgehog.Internal.Property (liftEither, liftExceptT)+import Hedgehog.Internal.Property (liftCatch, liftCatchIO, liftEither, liftExceptT) import Hedgehog.Internal.Property (Property, PropertyName, Group(..), GroupName) import Hedgehog.Internal.Property (ShrinkLimit, withShrinks) import Hedgehog.Internal.Property (Test, property) import Hedgehog.Internal.Property (TestLimit, withTests)-import Hedgehog.Internal.Property (withExceptT, withResourceT)+import Hedgehog.Internal.Property (withCatch, withExceptT, withResourceT)+import Hedgehog.Internal.Range (Range, Size(..)) import Hedgehog.Internal.Runner (check, recheck, checkSequential, checkParallel) import Hedgehog.Internal.Seed (Seed(..))+import Hedgehog.Internal.State (Command(..), Callback(..), Action)+import Hedgehog.Internal.State (executeSequential)+import Hedgehog.Internal.State (Var(..), Symbolic(..), Concrete(..)) import Hedgehog.Internal.TH (discover) import Hedgehog.Internal.Tripping (tripping)-import Hedgehog.Range (Range, Size(..))
src/Hedgehog/Gen.hs view
@@ -1,1304 +1,110 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-} -- MonadBase-module Hedgehog.Gen (- -- * Transformer- Gen(..)-- -- * Combinators- -- ** Shrinking- , shrink- , prune-- -- ** Size- , small- , scale- , resize- , sized-- -- ** Integral- , integral- , integral_-- , int- , int8- , int16- , int32- , int64-- , word- , word8- , word16- , word32- , word64-- -- ** Floating-point- , realFloat- , realFrac_- , float- , double-- -- ** Enumeration- , enum- , enumBounded- , bool- , bool_-- -- ** Characters- , binit- , octit- , digit- , hexit- , lower- , upper- , alpha- , alphaNum- , ascii- , latin1- , unicode- , unicodeAll-- -- ** Strings- , string- , text- , utf8- , bytes-- -- ** Choice- , constant- , element- , choice- , frequency- , recursive-- -- ** Conditional- , discard- , filter- , just-- -- ** Collections- , maybe- , list- , seq- , nonEmpty- , set- , map-- -- ** Subterms- , subterm- , subtermM- , subterm2- , subtermM2- , subterm3- , subtermM3-- -- ** Combinations & Permutations- , subsequence- , shuffle-- -- * Sampling Generators- , sample- , print- , printTree- , printWith- , printTreeWith-- -- * Internal- -- $internal-- -- ** Transfomer- , runGen- , mapGen- , generate- , liftTree- , runDiscardEffect-- -- ** Size- , golden-- -- ** Shrinking- , atLeast-- -- ** Characters- , isSurrogate-- -- ** Subterms- , Vec(..)- , Nat(..)- , subtermMVec- , freeze-- -- ** Sampling- , renderNodes- ) where--import Control.Applicative (Alternative(..))-import Control.Monad (MonadPlus(..), mfilter, filterM, replicateM, ap, join)-import Control.Monad.Base (MonadBase(..))-import Control.Monad.Catch (MonadThrow(..), MonadCatch(..))-import Control.Monad.Error.Class (MonadError(..))-import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Morph (MFunctor(..), MMonad(..))-import Control.Monad.Primitive (PrimMonad(..))-import Control.Monad.Reader.Class (MonadReader(..))-import Control.Monad.State.Class (MonadState(..))-import Control.Monad.Trans.Class (MonadTrans(..))-import Control.Monad.Trans.Maybe (MaybeT(..))-import Control.Monad.Trans.Resource (MonadResource(..))-import Control.Monad.Writer.Class (MonadWriter(..))--import Data.Bifunctor (first)-import Data.ByteString (ByteString)-import qualified Data.ByteString as ByteString-import qualified Data.Char as Char-import Data.Foldable (for_, toList)-import Data.Int (Int8, Int16, Int32, Int64)-import Data.List.NonEmpty (NonEmpty)-import qualified Data.List.NonEmpty as NonEmpty-import Data.Map (Map)-import qualified Data.Map as Map-import qualified Data.Maybe as Maybe-import Data.Sequence (Seq)-import qualified Data.Sequence as Seq-import Data.Set (Set)-import Data.Text (Text)-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import Data.Word (Word8, Word16, Word32, Word64)--import Hedgehog.Internal.Distributive (Distributive(..))-import Hedgehog.Internal.Seed (Seed)-import qualified Hedgehog.Internal.Seed as Seed-import qualified Hedgehog.Internal.Shrink as Shrink-import Hedgehog.Internal.Tree (Tree(..), Node(..))-import qualified Hedgehog.Internal.Tree as Tree-import Hedgehog.Range (Size, Range)-import qualified Hedgehog.Range as Range--import Prelude hiding (filter, print, maybe, map, seq)------------------------------------------------------------------------------ Generator transformer---- | Generator for random values of @a@.----newtype Gen m a =- Gen {- unGen :: Size -> Seed -> Tree (MaybeT m) a- }---- | Runs a generator, producing its shrink tree.----runGen :: Size -> Seed -> Gen m a -> Tree (MaybeT m) a-runGen size seed (Gen m) =- m size seed---- | Map over a generator's shrink tree.----mapGen :: (Tree (MaybeT m) a -> Tree (MaybeT n) b) -> Gen m a -> Gen n b-mapGen f gen =- Gen $ \size seed ->- f (runGen size seed gen)---- | Generate a value with no shrinks from a 'Size' and a 'Seed'.----generate :: Monad m => (Size -> Seed -> a) -> Gen m a-generate f =- Gen $ \size seed ->- pure (f size seed)---- | Freeze the size and seed used by a generator, so we can inspect the value--- which it will produce.------ This is used for implementing `list` and `subtermMVec`. It allows us to--- shrink the list itself before trying to shrink the values inside the list.----freeze :: Monad m => Gen m a -> Gen m (a, Gen m a)-freeze gen =- Gen $ \size seed -> do- mx <- lift . lift . runMaybeT . runTree $ runGen size seed gen- case mx of- Nothing ->- mzero- Just (Node x xs) ->- pure (x, liftTree . Tree.fromNode $ Node x xs)---- | Lift a predefined shrink tree in to a generator, ignoring the seed and the--- size.----liftTree :: Tree (MaybeT m) a -> Gen m a-liftTree x =- Gen (\_ _ -> x)---- | Run the discard effects through the tree and reify them as 'Maybe' values--- at the nodes. 'Nothing' means discarded, 'Just' means we have a value.----runDiscardEffect :: Monad m => Tree (MaybeT m) a -> Tree m (Maybe a)-runDiscardEffect =- runMaybeT . distribute----------------------------------------------------------------------------- Gen instances--instance Functor m => Functor (Gen m) where- fmap f gen =- Gen $ \seed size ->- fmap f (runGen seed size gen)--instance Monad m => Applicative (Gen m) where- pure =- return- (<*>) =- ap--instance Monad m => Monad (Gen m) where- return =- liftTree . pure-- (>>=) m k =- Gen $ \size seed ->- case Seed.split seed of- (sk, sm) ->- runGen size sk . k =<<- runGen size sm m--instance Monad m => Alternative (Gen m) where- empty =- mzero- (<|>) =- mplus--instance Monad m => MonadPlus (Gen m) where- mzero =- liftTree mzero-- mplus x y =- Gen $ \size seed ->- case Seed.split seed of- (sx, sy) ->- runGen size sx x `mplus`- runGen size sy y--instance MonadTrans Gen where- lift =- liftTree . lift . lift--instance MFunctor Gen where- hoist f =- mapGen (hoist (hoist f))--embedMaybe ::- MonadTrans t- => Monad n- => Monad (t (MaybeT n))- => (forall a. m a -> t (MaybeT n) a)- -> MaybeT m b- -> t (MaybeT n) b-embedMaybe f m =- lift . MaybeT . pure =<< f (runMaybeT m)--embedTree :: Monad n => (forall a. m a -> Tree (MaybeT n) a) -> Tree (MaybeT m) b -> Tree (MaybeT n) b-embedTree f tree =- embed (embedMaybe f) tree--embedGen :: Monad n => (forall a. m a -> Gen n a) -> Gen m b -> Gen n b-embedGen f gen =- Gen $ \size seed ->- case Seed.split seed of- (sf, sg) ->- (runGen size sf . f) `embedTree`- (runGen size sg gen)--instance MMonad Gen where- embed =- embedGen--distributeGen :: Transformer t Gen m => Gen (t m) a -> t (Gen m) a-distributeGen x =- join . lift . Gen $ \size seed ->- pure . hoist liftTree . distribute . hoist distribute $ runGen size seed x--instance Distributive Gen where- type Transformer t Gen m = (- Monad (t (Gen m))- , Transformer t MaybeT m- , Transformer t Tree (MaybeT m)- )-- distribute =- distributeGen--instance PrimMonad m => PrimMonad (Gen m) where- type PrimState (Gen m) =- PrimState m- primitive =- lift . primitive--instance MonadIO m => MonadIO (Gen m) where- liftIO =- lift . liftIO--instance MonadBase b m => MonadBase b (Gen m) where- liftBase =- lift . liftBase--instance MonadThrow m => MonadThrow (Gen m) where- throwM =- lift . throwM--instance MonadCatch m => MonadCatch (Gen m) where- catch m onErr =- Gen $ \size seed ->- case Seed.split seed of- (sm, se) ->- (runGen size sm m) `catch`- (runGen size se . onErr)--instance MonadReader r m => MonadReader r (Gen m) where- ask =- lift ask- local f m =- mapGen (local f) m--instance MonadState s m => MonadState s (Gen m) where- get =- lift get- put =- lift . put- state =- lift . state--instance MonadWriter w m => MonadWriter w (Gen m) where- writer =- lift . writer- tell =- lift . tell- listen =- mapGen listen- pass =- mapGen pass--instance MonadError e m => MonadError e (Gen m) where- throwError =- lift . throwError- catchError m onErr =- Gen $ \size seed ->- case Seed.split seed of- (sm, se) ->- (runGen size sm m) `catchError`- (runGen size se . onErr)--instance MonadResource m => MonadResource (Gen m) where- liftResourceT =- lift . liftResourceT----------------------------------------------------------------------------- Shrinking---- | Apply a shrinking function to a generator.------ This will give the generator additional shrinking options, while keeping--- the existing shrinks intact.----shrink :: Monad m => (a -> [a]) -> Gen m a -> Gen m a-shrink =- mapGen . Tree.expand---- | Throw away a generator's shrink tree.----prune :: Monad m => Gen m a -> Gen m a-prune =- mapGen Tree.prune----------------------------------------------------------------------------- Combinators - Size---- | Construct a generator that depends on the size parameter.----sized :: (Size -> Gen m a) -> Gen m a-sized f =- Gen $ \size seed ->- runGen size seed (f size)---- | Override the size parameter. Returns a generator which uses the given size--- instead of the runtime-size parameter.----resize :: Size -> Gen m a -> Gen m a-resize size gen =- if size < 0 then- error "Hedgehog.Random.resize: negative size"- else- Gen $ \_ seed ->- runGen size seed gen---- | Adjust the size parameter by transforming it with the given function.----scale :: (Size -> Size) -> Gen m a -> Gen m a-scale f gen =- sized $ \n ->- resize (f n) gen---- | Make a generator smaller by scaling its size parameter.----small :: Gen m a -> Gen m a-small =- scale golden---- | Scale a size using the golden ratio.------ > golden x = x / φ--- > golden x = x / 1.61803..----golden :: Size -> Size-golden x =- round (fromIntegral x * 0.61803398875 :: Double)----------------------------------------------------------------------------- Combinators - Integral---- | Generates a random integral number in the given @[inclusive,inclusive]@ range.------ When the generator tries to shrink, it will shrink towards the--- 'Range.origin' of the specified 'Range'.------ For example, the following generator will produce a number between @1970@--- and @2100@, but will shrink towards @2000@:------ @--- integral (Range.'Range.constantFrom' 2000 1970 2100) :: 'Gen' 'Int'--- @------ Some sample outputs from this generator might look like:------ > === Outcome ===--- > 1973--- > === Shrinks ===--- > 2000--- > 1987--- > 1980--- > 1976--- > 1974------ > === Outcome ===--- > 2061--- > === Shrinks ===--- > 2000--- > 2031--- > 2046--- > 2054--- > 2058--- > 2060----integral :: (Monad m, Integral a) => Range a -> Gen m a-integral range =- shrink (Shrink.towards $ Range.origin range) (integral_ range)---- | Generates a random integral number in the [inclusive,inclusive] range.------ /This generator does not shrink./----integral_ :: (Monad m, Integral a) => Range a -> Gen m a-integral_ range =- generate $ \size seed ->- let- (x, y) =- Range.bounds size range- in- fromInteger . fst $- Seed.nextInteger (toInteger x) (toInteger y) seed---- | Generates a random machine integer in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----int :: Monad m => Range Int -> Gen m Int-int =- integral---- | Generates a random 8-bit integer in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----int8 :: Monad m => Range Int8 -> Gen m Int8-int8 =- integral---- | Generates a random 16-bit integer in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----int16 :: Monad m => Range Int16 -> Gen m Int16-int16 =- integral---- | Generates a random 32-bit integer in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----int32 :: Monad m => Range Int32 -> Gen m Int32-int32 =- integral---- | Generates a random 64-bit integer in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----int64 :: Monad m => Range Int64 -> Gen m Int64-int64 =- integral---- | Generates a random machine word in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----word :: Monad m => Range Word -> Gen m Word-word =- integral---- | Generates a random byte in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----word8 :: Monad m => Range Word8 -> Gen m Word8-word8 =- integral---- | Generates a random 16-bit word in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----word16 :: Monad m => Range Word16 -> Gen m Word16-word16 =- integral---- | Generates a random 32-bit word in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----word32 :: Monad m => Range Word32 -> Gen m Word32-word32 =- integral---- | Generates a random 64-bit word in the given @[inclusive,inclusive]@ range.------ /This is a specialization of 'integral', offered for convenience./----word64 :: Monad m => Range Word64 -> Gen m Word64-word64 =- integral----------------------------------------------------------------------------- Combinators - Fractional / Floating-Point---- | Generates a random floating-point number in the @[inclusive,exclusive)@ range.------ This generator works the same as 'integral', but for floating point numbers.----realFloat :: (Monad m, RealFloat a) => Range a -> Gen m a-realFloat range =- shrink (Shrink.towardsFloat $ Range.origin range) (realFrac_ range)---- | Generates a random fractional number in the [inclusive,exclusive) range.------ /This generator does not shrink./----realFrac_ :: (Monad m, RealFrac a) => Range a -> Gen m a-realFrac_ range =- generate $ \size seed ->- let- (x, y) =- Range.bounds size range- in- realToFrac . fst $- Seed.nextDouble (realToFrac x) (realToFrac y) seed---- | Generates a random floating-point number in the @[inclusive,exclusive)@ range.------ /This is a specialization of 'realFloat', offered for convenience./----float :: Monad m => Range Float -> Gen m Float-float =- realFloat---- | Generates a random floating-point number in the @[inclusive,exclusive)@ range.------ /This is a specialization of 'realFloat', offered for convenience./----double :: Monad m => Range Double -> Gen m Double-double =- realFloat----------------------------------------------------------------------------- Combinators - Enumeration---- | Generates an element from an enumeration.------ This generator shrinks towards the first argument.------ For example:------ @--- enum \'a' \'z' :: 'Gen' 'Char'--- @----enum :: (Monad m, Enum a) => a -> a -> Gen m a-enum lo hi =- fmap toEnum . integral $- Range.constant (fromEnum lo) (fromEnum hi)---- | Generates a random value from a bounded enumeration.------ This generator shrinks towards 'minBound'.------ For example:------ @--- enumBounded :: 'Gen' 'Bool'--- @----enumBounded :: (Monad m, Enum a, Bounded a) => Gen m a-enumBounded =- enum minBound maxBound---- | Generates a random boolean.------ This generator shrinks to 'False'.------ /This is a specialization of 'enumBounded', offered for convenience./----bool :: Monad m => Gen m Bool-bool =- enumBounded---- | Generates a random boolean.------ /This generator does not shrink./----bool_ :: Monad m => Gen m Bool-bool_ =- generate $ \_ seed ->- (/= 0) . fst $ Seed.nextInteger 0 1 seed----------------------------------------------------------------------------- Combinators - Characters---- | Generates an ASCII binit: @'0'..'1'@----binit :: Monad m => Gen m Char-binit =- enum '0' '1'---- | Generates an ASCII octit: @'0'..'7'@----octit :: Monad m => Gen m Char-octit =- enum '0' '7'---- | Generates an ASCII digit: @'0'..'9'@----digit :: Monad m => Gen m Char-digit =- enum '0' '9'---- | Generates an ASCII hexit: @'0'..'9', \'a\'..\'f\', \'A\'..\'F\'@----hexit :: Monad m => Gen m Char-hexit =- -- FIXME optimize lookup, use a SmallArray or something.- element "0123456789aAbBcCdDeEfF"---- | Generates an ASCII lowercase letter: @\'a\'..\'z\'@----lower :: Monad m => Gen m Char-lower =- enum 'a' 'z'---- | Generates an ASCII uppercase letter: @\'A\'..\'Z\'@----upper :: Monad m => Gen m Char-upper =- enum 'A' 'Z'---- | Generates an ASCII letter: @\'a\'..\'z\', \'A\'..\'Z\'@----alpha :: Monad m => Gen m Char-alpha =- -- FIXME optimize lookup, use a SmallArray or something.- element "abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"---- | Generates an ASCII letter or digit: @\'a\'..\'z\', \'A\'..\'Z\', \'0\'..\'9\'@----alphaNum :: Monad m => Gen m Char-alphaNum =- -- FIXME optimize lookup, use a SmallArray or something.- element "abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"---- | Generates an ASCII character: @'\0'..'\127'@----ascii :: Monad m => Gen m Char-ascii =- enum '\0' '\127'---- | Generates a Latin-1 character: @'\0'..'\255'@----latin1 :: Monad m => Gen m Char-latin1 =- enum '\0' '\255'---- | Generates a Unicode character, excluding invalid standalone surrogates:--- @'\0'..'\1114111' (excluding '\55296'..'\57343')@----unicode :: Monad m => Gen m Char-unicode =- filter (not . isSurrogate) unicodeAll---- | Generates a Unicode character, including invalid standalone surrogates:--- @'\0'..'\1114111'@----unicodeAll :: Monad m => Gen m Char-unicodeAll =- enumBounded---- | Check if a character is in the surrogate category.----isSurrogate :: Char -> Bool-isSurrogate x =- x >= '\55296' && x <= '\57343'----------------------------------------------------------------------------- Combinators - Strings---- | Generates a string using 'Range' to determine the length.------ /This is a specialization of 'list', offered for convenience./----string :: Monad m => Range Int -> Gen m Char -> Gen m String-string =- list---- | Generates a string using 'Range' to determine the length.----text :: Monad m => Range Int -> Gen m Char -> Gen m Text-text range =- fmap Text.pack . string range---- | Generates a UTF-8 encoded string, using 'Range' to determine the length.----utf8 :: Monad m => Range Int -> Gen m Char -> Gen m ByteString-utf8 range =- fmap Text.encodeUtf8 . text range---- | Generates a random 'ByteString', using 'Range' to determine the--- length.----bytes :: Monad m => Range Int -> Gen m ByteString-bytes range =- fmap ByteString.pack $- choice [- list range . word8 $- Range.constant- (fromIntegral $ Char.ord 'a')- (fromIntegral $ Char.ord 'z')-- , list range . word8 $- Range.constant minBound maxBound- ]----------------------------------------------------------------------------- Combinators - Choice---- | Trivial generator that always produces the same element.------ /This is another name for 'pure' \/ 'return'./-constant :: Monad m => a -> Gen m a-constant =- pure---- | Randomly selects one of the elements in the list.------ This generator shrinks towards the first element in the list.------ /The input list must be non-empty./----element :: Monad m => [a] -> Gen m a-element = \case- [] ->- error "Hedgehog.Gen.element: used with empty list"- xs -> do- n <- integral $ Range.constant 0 (length xs - 1)- pure $ xs !! n---- | Randomly selects one of the generators in the list.------ This generator shrinks towards the first generator in the list.------ /The input list must be non-empty./----choice :: Monad m => [Gen m a] -> Gen m a-choice = \case- [] ->- error "Hedgehog.Gen.choice: used with empty list"- xs -> do- n <- integral $ Range.constant 0 (length xs - 1)- xs !! n---- | Uses a weighted distribution to randomly select one of the generators in--- the list.------ This generator shrinks towards the first generator in the list.------ /The input list must be non-empty./----frequency :: Monad m => [(Int, Gen m a)] -> Gen m a-frequency = \case- [] ->- error "Hedgehog.Gen.frequency: used with empty list"- xs0 -> do- let- pick n = \case- [] ->- error "Hedgehog.Gen.frequency/pick: used with empty list"- (k, x) : xs ->- if n <= k then- x- else- pick (n - k) xs-- total =- sum (fmap fst xs0)-- n <- integral $ Range.constant 1 total- pick n xs0---- | Modifies combinators which choose from a list of generators, like 'choice'--- or 'frequency', so that they can be used in recursive scenarios.------ This combinator modifies its target to select one of the generators in--- either the non-recursive or the recursive list. When a selection is made--- from the recursive list, the 'Size' is halved. When the 'Size' gets to one--- or less, selections are no longer made from the recursive list, this--- ensures termination.------ A good example of where this might be useful is abstract syntax trees:------ @--- data Expr =--- Var String--- | Lam String Expr--- | App Expr Expr------ -- Assuming we have a name generator--- genName :: 'Monad' m => 'Gen' m String------ -- We can write a generator for expressions--- genExpr :: 'Monad' m => 'Gen' m Expr--- genExpr =--- Gen.'recursive' Gen.'choice' [--- -- non-recursive generators--- Var '<$>' genName--- ] [--- -- recursive generators--- Gen.'subtermM' genExpr (\x -> Lam '<$>' genName '<*>' pure x)--- , Gen.'subterm2' genExpr genExpr App--- ]--- @------ If we wrote the above example using only 'choice', it is likely that it--- would fail to terminate. This is because for every call to @genExpr@,--- there is a 2 in 3 chance that we will recurse again.----recursive :: ([Gen m a] -> Gen m a) -> [Gen m a] -> [Gen m a] -> Gen m a-recursive f nonrec rec =- sized $ \n ->- if n <= 1 then- f nonrec- else- f $ nonrec ++ fmap small rec----------------------------------------------------------------------------- Combinators - Conditional---- | Discards the whole generator.------ /This is another name for 'empty' \/ 'mzero'./----discard :: Monad m => Gen m a-discard =- mzero---- | Generates a value that satisfies a predicate.------ This is essentially:------ @--- filter p gen = 'mfilter' p gen '<|>' filter p gen--- @------ It differs from the above in that we keep some state to avoid looping--- forever. If we trigger these limits then the whole generator is discarded.----filter :: Monad m => (a -> Bool) -> Gen m a -> Gen m a-filter p gen =- let- try k =- if k > 100 then- empty- else- mfilter p (scale (2 * k +) gen) <|> try (k + 1)- in- try 0---- | Runs a 'Maybe' generator until it produces a 'Just'.------ This is implemented using 'filter' and has the same caveats.----just :: Monad m => Gen m (Maybe a) -> Gen m a-just g = do- mx <- filter Maybe.isJust g- case mx of- Just x ->- pure x- Nothing ->- error "Hedgehog.Gen.just: internal error, unexpected Nothing"----------------------------------------------------------------------------- Combinators - Collections---- | Generates a 'Nothing' some of the time.----maybe :: Monad m => Gen m a -> Gen m (Maybe a)-maybe gen =- sized $ \n ->- frequency [- (2, pure Nothing)- , (1 + fromIntegral n, Just <$> gen)- ]---- | Generates a list using a 'Range' to determine the length.----list :: Monad m => Range Int -> Gen m a -> Gen m [a]-list range gen =- sized $ \size ->- (traverse snd =<<) .- mfilter (atLeast $ Range.lowerBound size range) .- shrink Shrink.list $ do- k <- integral_ range- replicateM k (freeze gen)---- | Generates a seq using a 'Range' to determine the length.----seq :: Monad m => Range Int -> Gen m a -> Gen m (Seq a)-seq range gen =- Seq.fromList <$> list range gen---- | Generates a non-empty list using a 'Range' to determine the length.----nonEmpty :: Monad m => Range Int -> Gen m a -> Gen m (NonEmpty a)-nonEmpty range gen = do- xs <- list (fmap (max 1) range) gen- case xs of- [] ->- error "Hedgehog.Gen.nonEmpty: internal error, generated empty list"- _ ->- pure $ NonEmpty.fromList xs---- | Generates a set using a 'Range' to determine the length.------ /This may fail to generate anything if the element generator/--- /cannot produce a large enough number of unique items to satify/--- /the required set size./----set :: (Monad m, Ord a) => Range Int -> Gen m a -> Gen m (Set a)-set range gen =- fmap Map.keysSet . map range $ fmap (, ()) gen---- | Generates a map using a 'Range' to determine the length.------ /This may fail to generate anything if the keys produced by the/--- /generator do not account for a large enough number of unique/--- /items to satify the required map size./----map :: (Monad m, Ord k) => Range Int -> Gen m (k, v) -> Gen m (Map k v)-map range gen =- sized $ \size ->- mfilter ((>= Range.lowerBound size range) . Map.size) .- fmap Map.fromList .- (sequence =<<) .- shrink Shrink.list $ do- k <- integral_ range- uniqueByKey k gen---- | Generate exactly 'n' unique generators.----uniqueByKey :: (Monad m, Ord k) => Int -> Gen m (k, v) -> Gen m [Gen m (k, v)]-uniqueByKey n gen =- let- try k xs0 =- if k > 100 then- mzero- else- replicateM n (freeze gen) >>= \kvs ->- case uniqueInsert n xs0 (fmap (first fst) kvs) of- Left xs ->- pure $ Map.elems xs- Right xs ->- try (k + 1) xs- in- try (0 :: Int) Map.empty--uniqueInsert :: Ord k => Int -> Map k v -> [(k, v)] -> Either (Map k v) (Map k v)-uniqueInsert n xs kvs0 =- if Map.size xs >= n then- Left xs- else- case kvs0 of- [] ->- Right xs- (k, v) : kvs ->- uniqueInsert n (Map.insertWith (\x _ -> x) k v xs) kvs---- | Check that list contains at least a certain number of elements.----atLeast :: Int -> [a] -> Bool-atLeast n =- if n == 0 then- const True- else- not . null . drop (n - 1)----------------------------------------------------------------------------- Combinators - Subterms--data Subterms n a =- One a- | All (Vec n a)- deriving (Functor, Foldable, Traversable)--data Nat =- Z- | S Nat--data Vec n a where- Nil :: Vec 'Z a- (:.) :: a -> Vec n a -> Vec ('S n) a--infixr 5 :.--deriving instance Functor (Vec n)-deriving instance Foldable (Vec n)-deriving instance Traversable (Vec n)--shrinkSubterms :: Subterms n a -> [Subterms n a]-shrinkSubterms = \case- One _ ->- []- All xs ->- fmap One $ toList xs--genSubterms :: Monad m => Vec n (Gen m a) -> Gen m (Subterms n a)-genSubterms =- (sequence =<<) .- shrink shrinkSubterms .- fmap All .- mapM (fmap snd . freeze)--fromSubterms :: Applicative m => (Vec n a -> m a) -> Subterms n a -> m a-fromSubterms f = \case- One x ->- pure x- All xs ->- f xs---- | Constructs a generator from a number of sub-term generators.------ /Shrinks to one of the sub-terms if possible./----subtermMVec :: Monad m => Vec n (Gen m a) -> (Vec n a -> Gen m a) -> Gen m a-subtermMVec gs f =- fromSubterms f =<< genSubterms gs---- | Constructs a generator from a sub-term generator.------ /Shrinks to the sub-term if possible./----subtermM :: Monad m => Gen m a -> (a -> Gen m a) -> Gen m a-subtermM gx f =- subtermMVec (gx :. Nil) $ \(x :. Nil) ->- f x---- | Constructs a generator from a sub-term generator.------ /Shrinks to the sub-term if possible./----subterm :: Monad m => Gen m a -> (a -> a) -> Gen m a-subterm gx f =- subtermM gx $ \x ->- pure (f x)---- | Constructs a generator from two sub-term generators.------ /Shrinks to one of the sub-terms if possible./----subtermM2 :: Monad m => Gen m a -> Gen m a -> (a -> a -> Gen m a) -> Gen m a-subtermM2 gx gy f =- subtermMVec (gx :. gy :. Nil) $ \(x :. y :. Nil) ->- f x y---- | Constructs a generator from two sub-term generators.------ /Shrinks to one of the sub-terms if possible./----subterm2 :: Monad m => Gen m a -> Gen m a -> (a -> a -> a) -> Gen m a-subterm2 gx gy f =- subtermM2 gx gy $ \x y ->- pure (f x y)---- | Constructs a generator from three sub-term generators.------ /Shrinks to one of the sub-terms if possible./----subtermM3 :: Monad m => Gen m a -> Gen m a -> Gen m a -> (a -> a -> a -> Gen m a) -> Gen m a-subtermM3 gx gy gz f =- subtermMVec (gx :. gy :. gz :. Nil) $ \(x :. y :. z :. Nil) ->- f x y z---- | Constructs a generator from three sub-term generators.------ /Shrinks to one of the sub-terms if possible./----subterm3 :: Monad m => Gen m a -> Gen m a -> Gen m a -> (a -> a -> a -> a) -> Gen m a-subterm3 gx gy gz f =- subtermM3 gx gy gz $ \x y z ->- pure (f x y z)----------------------------------------------------------------------------- Combinators - Combinations & Permutations---- | Generates a random subsequence of a list.----subsequence :: Monad m => [a] -> Gen m [a]-subsequence xs =- shrink Shrink.list $ filterM (const bool_) xs---- | Generates a random permutation of a list.------ This shrinks towards the order of the list being identical to the input--- list.----shuffle :: Monad m => [a] -> Gen m [a]-shuffle = \case- [] ->- pure []- xs0 -> do- n <- integral $ Range.constant 0 (length xs0 - 1)- case splitAt n xs0 of- (xs, y : ys) ->- (y :) <$> shuffle (xs ++ ys)- (_, []) ->- error "Hedgehog.shuffle: internal error, split generated empty list"----------------------------------------------------------------------------- Sampling---- | Generate a random sample of data from the a generator.----sample :: MonadIO m => Gen m a -> m [a]-sample gen =- fmap (fmap nodeValue . Maybe.catMaybes) .- replicateM 10 $ do- seed <- liftIO Seed.random- runMaybeT . runTree $ runGen 30 seed gen---- | Print the value produced by a generator, and the first level of shrinks,--- for the given size and seed.------ Use 'print' to generate a value from a random seed.----printWith :: (MonadIO m, Show a) => Size -> Seed -> Gen m a -> m ()-printWith size seed gen = do- Node x ss <- runTree $ renderNodes size seed gen- liftIO $ putStrLn "=== Outcome ==="- liftIO $ putStrLn x- liftIO $ putStrLn "=== Shrinks ==="- for_ ss $ \s -> do- Node y _ <- runTree s- liftIO $ putStrLn y---- | Print the shrink tree produced by a generator, for the given size and--- seed.------ Use 'printTree' to generate a value from a random seed.----printTreeWith :: (MonadIO m, Show a) => Size -> Seed -> Gen m a -> m ()-printTreeWith size seed gen = do- s <- Tree.render $ renderNodes size seed gen- liftIO $ putStr s---- | Run a generator with a random seed and print the outcome, and the first--- level of shrinks.------ @--- Gen.print (Gen.'enum' \'a\' \'f\')--- @------ > === Outcome ===--- > 'd'--- > === Shrinks ===--- > 'a'--- > 'b'--- > 'c'----print :: (MonadIO m, Show a) => Gen m a -> m ()-print gen = do- seed <- liftIO Seed.random- printWith 30 seed gen---- | Run a generator with a random seed and print the resulting shrink tree.------ @--- Gen.printTree (Gen.'enum' \'a\' \'f\')--- @------ > 'd'--- > ├╼'a'--- > ├╼'b'--- > │ └╼'a'--- > └╼'c'--- > ├╼'a'--- > └╼'b'--- > └╼'a'------ /This may not terminate when the tree is very large./----printTree :: (MonadIO m, Show a) => Gen m a -> m ()-printTree gen = do- seed <- liftIO Seed.random- printTreeWith 30 seed gen---- | Render a generator as a tree of strings.----renderNodes :: (Monad m, Show a) => Size -> Seed -> Gen m a -> Tree m String-renderNodes size seed =- fmap (Maybe.maybe "<discard>" show) . runDiscardEffect . runGen size seed----------------------------------------------------------------------------- Internal---- $internal------ These functions are exported in case you need them in a pinch, but are not--- part of the public API and may change at any time, even as part of a minor--- update.+module Hedgehog.Gen (+ -- * Transformer+ Gen++ -- * Combinators+ -- ** Shrinking+ , shrink+ , prune++ -- ** Size+ , small+ , scale+ , resize+ , sized++ -- ** Integral+ , integral+ , integral_++ , int+ , int8+ , int16+ , int32+ , int64++ , word+ , word8+ , word16+ , word32+ , word64++ -- ** Floating-point+ , realFloat+ , realFrac_+ , float+ , double++ -- ** Enumeration+ , enum+ , enumBounded+ , bool+ , bool_++ -- ** Characters+ , binit+ , octit+ , digit+ , hexit+ , lower+ , upper+ , alpha+ , alphaNum+ , ascii+ , latin1+ , unicode+ , unicodeAll++ -- ** Strings+ , string+ , text+ , utf8+ , bytes++ -- ** Choice+ , constant+ , element+ , choice+ , frequency+ , recursive++ -- ** Conditional+ , discard+ , filter+ , just++ -- ** Collections+ , maybe+ , list+ , seq+ , nonEmpty+ , set+ , map++ -- ** Subterms+ , subterm+ , subtermM+ , subterm2+ , subtermM2+ , subterm3+ , subtermM3++ -- ** Combinations & Permutations+ , subsequence+ , shuffle++ -- ** Abstract State Machine+ , actions++ -- * Sampling Generators+ , sample+ , print+ , printTree+ , printWith+ , printTreeWith+ ) where++import Hedgehog.Internal.Gen+import Hedgehog.Internal.State (actions)++import Prelude hiding (filter, print, maybe, map, seq)
src/Hedgehog/Internal/Distributive.hs view
@@ -31,6 +31,8 @@ , MFunctor f ) + -- | Distribute one monad transformer over another.+ -- distribute :: Transformer f g m => g (f m) a -> f (g m) a instance Distributive MaybeT where
+ src/Hedgehog/Internal/Exception.hs view
@@ -0,0 +1,42 @@+module Hedgehog.Internal.Exception (+ TypedException(..)+ , tryAll+ ) where++import Control.Exception (Exception(..), AsyncException, SomeException(..))+import Control.Monad.Catch (MonadCatch(..), throwM)++import Data.Typeable (typeOf)+++-- | Newtype for 'SomeException' with a 'Show' instance that only contains+-- valid Haskell 98 tokens and also includes the type of the exception.+--+-- For example, when catching the exception thrown by @fail "foo" :: IO ()@+-- and calling show:+--+-- @+-- IOException "user error (foo)"+-- @+--+-- Having access to the type can be useful when trying to track down the+-- source of an exception.+--+newtype TypedException =+ TypedException SomeException++instance Show TypedException where+ showsPrec p (TypedException (SomeException x)) =+ showParen (p > 10) $+ showsPrec 11 (typeOf x) .+ showChar ' ' .+ showsPrec 11 (displayException x)++tryAll :: MonadCatch m => m a -> m (Either TypedException a)+tryAll m =+ catch (fmap Right m) $ \exception ->+ case fromException exception :: Maybe AsyncException of+ Nothing ->+ pure . Left $ TypedException exception+ Just async ->+ throwM async
+ src/Hedgehog/Internal/Gen.hs view
@@ -0,0 +1,1304 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} -- MonadBase+module Hedgehog.Internal.Gen (+ -- * Transformer+ Gen(..)++ -- * Combinators+ -- ** Shrinking+ , shrink+ , prune++ -- ** Size+ , small+ , scale+ , resize+ , sized++ -- ** Integral+ , integral+ , integral_++ , int+ , int8+ , int16+ , int32+ , int64++ , word+ , word8+ , word16+ , word32+ , word64++ -- ** Floating-point+ , realFloat+ , realFrac_+ , float+ , double++ -- ** Enumeration+ , enum+ , enumBounded+ , bool+ , bool_++ -- ** Characters+ , binit+ , octit+ , digit+ , hexit+ , lower+ , upper+ , alpha+ , alphaNum+ , ascii+ , latin1+ , unicode+ , unicodeAll++ -- ** Strings+ , string+ , text+ , utf8+ , bytes++ -- ** Choice+ , constant+ , element+ , choice+ , frequency+ , recursive++ -- ** Conditional+ , discard+ , filter+ , just++ -- ** Collections+ , maybe+ , list+ , seq+ , nonEmpty+ , set+ , map++ -- ** Subterms+ , freeze+ , subterm+ , subtermM+ , subterm2+ , subtermM2+ , subterm3+ , subtermM3++ -- ** Combinations & Permutations+ , subsequence+ , shuffle++ -- * Sampling Generators+ , sample+ , print+ , printTree+ , printWith+ , printTreeWith++ -- * Internal+ -- $internal++ -- ** Transfomer+ , runGen+ , mapGen+ , generate+ , liftTree+ , runDiscardEffect++ -- ** Size+ , golden++ -- ** Shrinking+ , atLeast++ -- ** Characters+ , isSurrogate++ -- ** Subterms+ , Vec(..)+ , Nat(..)+ , subtermMVec++ -- ** Sampling+ , renderNodes+ ) where++import Control.Applicative (Alternative(..))+import Control.Monad (MonadPlus(..), mfilter, filterM, replicateM, ap, join)+import Control.Monad.Base (MonadBase(..))+import Control.Monad.Catch (MonadThrow(..), MonadCatch(..))+import Control.Monad.Error.Class (MonadError(..))+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Morph (MFunctor(..), MMonad(..))+import Control.Monad.Primitive (PrimMonad(..))+import Control.Monad.Reader.Class (MonadReader(..))+import Control.Monad.State.Class (MonadState(..))+import Control.Monad.Trans.Class (MonadTrans(..))+import Control.Monad.Trans.Maybe (MaybeT(..))+import Control.Monad.Trans.Resource (MonadResource(..))+import Control.Monad.Writer.Class (MonadWriter(..))++import Data.Bifunctor (first)+import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import qualified Data.Char as Char+import Data.Foldable (for_, toList)+import Data.Int (Int8, Int16, Int32, Int64)+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NonEmpty+import Data.Map (Map)+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Data.Set (Set)+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import Data.Word (Word8, Word16, Word32, Word64)++import Hedgehog.Internal.Distributive (Distributive(..))+import Hedgehog.Internal.Seed (Seed)+import qualified Hedgehog.Internal.Seed as Seed+import qualified Hedgehog.Internal.Shrink as Shrink+import Hedgehog.Internal.Tree (Tree(..), Node(..))+import qualified Hedgehog.Internal.Tree as Tree+import Hedgehog.Range (Size, Range)+import qualified Hedgehog.Range as Range++import Prelude hiding (filter, print, maybe, map, seq)+++------------------------------------------------------------------------+-- Generator transformer++-- | Generator for random values of @a@.+--+newtype Gen m a =+ Gen {+ unGen :: Size -> Seed -> Tree (MaybeT m) a+ }++-- | Runs a generator, producing its shrink tree.+--+runGen :: Size -> Seed -> Gen m a -> Tree (MaybeT m) a+runGen size seed (Gen m) =+ m size seed++-- | Map over a generator's shrink tree.+--+mapGen :: (Tree (MaybeT m) a -> Tree (MaybeT n) b) -> Gen m a -> Gen n b+mapGen f gen =+ Gen $ \size seed ->+ f (runGen size seed gen)++-- | Generate a value with no shrinks from a 'Size' and a 'Seed'.+--+generate :: Monad m => (Size -> Seed -> a) -> Gen m a+generate f =+ Gen $ \size seed ->+ pure (f size seed)++-- | Freeze the size and seed used by a generator, so we can inspect the value+-- which it will produce.+--+-- This is used for implementing `list` and `subtermMVec`. It allows us to+-- shrink the list itself before trying to shrink the values inside the list.+--+freeze :: Monad m => Gen m a -> Gen m (a, Gen m a)+freeze gen =+ Gen $ \size seed -> do+ mx <- lift . lift . runMaybeT . runTree $ runGen size seed gen+ case mx of+ Nothing ->+ mzero+ Just (Node x xs) ->+ pure (x, liftTree . Tree.fromNode $ Node x xs)++-- | Lift a predefined shrink tree in to a generator, ignoring the seed and the+-- size.+--+liftTree :: Tree (MaybeT m) a -> Gen m a+liftTree x =+ Gen (\_ _ -> x)++-- | Run the discard effects through the tree and reify them as 'Maybe' values+-- at the nodes. 'Nothing' means discarded, 'Just' means we have a value.+--+runDiscardEffect :: Monad m => Tree (MaybeT m) a -> Tree m (Maybe a)+runDiscardEffect =+ runMaybeT . distribute++------------------------------------------------------------------------+-- Gen instances++instance Functor m => Functor (Gen m) where+ fmap f gen =+ Gen $ \seed size ->+ fmap f (runGen seed size gen)++instance Monad m => Applicative (Gen m) where+ pure =+ return+ (<*>) =+ ap++instance Monad m => Monad (Gen m) where+ return =+ liftTree . pure++ (>>=) m k =+ Gen $ \size seed ->+ case Seed.split seed of+ (sk, sm) ->+ runGen size sk . k =<<+ runGen size sm m++instance Monad m => Alternative (Gen m) where+ empty =+ mzero+ (<|>) =+ mplus++instance Monad m => MonadPlus (Gen m) where+ mzero =+ liftTree mzero++ mplus x y =+ Gen $ \size seed ->+ case Seed.split seed of+ (sx, sy) ->+ runGen size sx x `mplus`+ runGen size sy y++instance MonadTrans Gen where+ lift =+ liftTree . lift . lift++instance MFunctor Gen where+ hoist f =+ mapGen (hoist (hoist f))++embedMaybe ::+ MonadTrans t+ => Monad n+ => Monad (t (MaybeT n))+ => (forall a. m a -> t (MaybeT n) a)+ -> MaybeT m b+ -> t (MaybeT n) b+embedMaybe f m =+ lift . MaybeT . pure =<< f (runMaybeT m)++embedTree :: Monad n => (forall a. m a -> Tree (MaybeT n) a) -> Tree (MaybeT m) b -> Tree (MaybeT n) b+embedTree f tree =+ embed (embedMaybe f) tree++embedGen :: Monad n => (forall a. m a -> Gen n a) -> Gen m b -> Gen n b+embedGen f gen =+ Gen $ \size seed ->+ case Seed.split seed of+ (sf, sg) ->+ (runGen size sf . f) `embedTree`+ (runGen size sg gen)++instance MMonad Gen where+ embed =+ embedGen++distributeGen :: Transformer t Gen m => Gen (t m) a -> t (Gen m) a+distributeGen x =+ join . lift . Gen $ \size seed ->+ pure . hoist liftTree . distribute . hoist distribute $ runGen size seed x++instance Distributive Gen where+ type Transformer t Gen m = (+ Monad (t (Gen m))+ , Transformer t MaybeT m+ , Transformer t Tree (MaybeT m)+ )++ distribute =+ distributeGen++instance PrimMonad m => PrimMonad (Gen m) where+ type PrimState (Gen m) =+ PrimState m+ primitive =+ lift . primitive++instance MonadIO m => MonadIO (Gen m) where+ liftIO =+ lift . liftIO++instance MonadBase b m => MonadBase b (Gen m) where+ liftBase =+ lift . liftBase++instance MonadThrow m => MonadThrow (Gen m) where+ throwM =+ lift . throwM++instance MonadCatch m => MonadCatch (Gen m) where+ catch m onErr =+ Gen $ \size seed ->+ case Seed.split seed of+ (sm, se) ->+ (runGen size sm m) `catch`+ (runGen size se . onErr)++instance MonadReader r m => MonadReader r (Gen m) where+ ask =+ lift ask+ local f m =+ mapGen (local f) m++instance MonadState s m => MonadState s (Gen m) where+ get =+ lift get+ put =+ lift . put+ state =+ lift . state++instance MonadWriter w m => MonadWriter w (Gen m) where+ writer =+ lift . writer+ tell =+ lift . tell+ listen =+ mapGen listen+ pass =+ mapGen pass++instance MonadError e m => MonadError e (Gen m) where+ throwError =+ lift . throwError+ catchError m onErr =+ Gen $ \size seed ->+ case Seed.split seed of+ (sm, se) ->+ (runGen size sm m) `catchError`+ (runGen size se . onErr)++instance MonadResource m => MonadResource (Gen m) where+ liftResourceT =+ lift . liftResourceT++------------------------------------------------------------------------+-- Shrinking++-- | Apply a shrinking function to a generator.+--+-- This will give the generator additional shrinking options, while keeping+-- the existing shrinks intact.+--+shrink :: Monad m => (a -> [a]) -> Gen m a -> Gen m a+shrink =+ mapGen . Tree.expand++-- | Throw away a generator's shrink tree.+--+prune :: Monad m => Gen m a -> Gen m a+prune =+ mapGen Tree.prune++------------------------------------------------------------------------+-- Combinators - Size++-- | Construct a generator that depends on the size parameter.+--+sized :: (Size -> Gen m a) -> Gen m a+sized f =+ Gen $ \size seed ->+ runGen size seed (f size)++-- | Override the size parameter. Returns a generator which uses the given size+-- instead of the runtime-size parameter.+--+resize :: Size -> Gen m a -> Gen m a+resize size gen =+ if size < 0 then+ error "Hedgehog.Random.resize: negative size"+ else+ Gen $ \_ seed ->+ runGen size seed gen++-- | Adjust the size parameter by transforming it with the given function.+--+scale :: (Size -> Size) -> Gen m a -> Gen m a+scale f gen =+ sized $ \n ->+ resize (f n) gen++-- | Make a generator smaller by scaling its size parameter.+--+small :: Gen m a -> Gen m a+small =+ scale golden++-- | Scale a size using the golden ratio.+--+-- > golden x = x / φ+-- > golden x = x / 1.61803..+--+golden :: Size -> Size+golden x =+ round (fromIntegral x * 0.61803398875 :: Double)++------------------------------------------------------------------------+-- Combinators - Integral++-- | Generates a random integral number in the given @[inclusive,inclusive]@ range.+--+-- When the generator tries to shrink, it will shrink towards the+-- 'Range.origin' of the specified 'Range'.+--+-- For example, the following generator will produce a number between @1970@+-- and @2100@, but will shrink towards @2000@:+--+-- @+-- integral (Range.'Range.constantFrom' 2000 1970 2100) :: 'Gen' 'Int'+-- @+--+-- Some sample outputs from this generator might look like:+--+-- > === Outcome ===+-- > 1973+-- > === Shrinks ===+-- > 2000+-- > 1987+-- > 1980+-- > 1976+-- > 1974+--+-- > === Outcome ===+-- > 2061+-- > === Shrinks ===+-- > 2000+-- > 2031+-- > 2046+-- > 2054+-- > 2058+-- > 2060+--+integral :: (Monad m, Integral a) => Range a -> Gen m a+integral range =+ shrink (Shrink.towards $ Range.origin range) (integral_ range)++-- | Generates a random integral number in the [inclusive,inclusive] range.+--+-- /This generator does not shrink./+--+integral_ :: (Monad m, Integral a) => Range a -> Gen m a+integral_ range =+ generate $ \size seed ->+ let+ (x, y) =+ Range.bounds size range+ in+ fromInteger . fst $+ Seed.nextInteger (toInteger x) (toInteger y) seed++-- | Generates a random machine integer in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+int :: Monad m => Range Int -> Gen m Int+int =+ integral++-- | Generates a random 8-bit integer in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+int8 :: Monad m => Range Int8 -> Gen m Int8+int8 =+ integral++-- | Generates a random 16-bit integer in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+int16 :: Monad m => Range Int16 -> Gen m Int16+int16 =+ integral++-- | Generates a random 32-bit integer in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+int32 :: Monad m => Range Int32 -> Gen m Int32+int32 =+ integral++-- | Generates a random 64-bit integer in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+int64 :: Monad m => Range Int64 -> Gen m Int64+int64 =+ integral++-- | Generates a random machine word in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+word :: Monad m => Range Word -> Gen m Word+word =+ integral++-- | Generates a random byte in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+word8 :: Monad m => Range Word8 -> Gen m Word8+word8 =+ integral++-- | Generates a random 16-bit word in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+word16 :: Monad m => Range Word16 -> Gen m Word16+word16 =+ integral++-- | Generates a random 32-bit word in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+word32 :: Monad m => Range Word32 -> Gen m Word32+word32 =+ integral++-- | Generates a random 64-bit word in the given @[inclusive,inclusive]@ range.+--+-- /This is a specialization of 'integral', offered for convenience./+--+word64 :: Monad m => Range Word64 -> Gen m Word64+word64 =+ integral++------------------------------------------------------------------------+-- Combinators - Fractional / Floating-Point++-- | Generates a random floating-point number in the @[inclusive,exclusive)@ range.+--+-- This generator works the same as 'integral', but for floating point numbers.+--+realFloat :: (Monad m, RealFloat a) => Range a -> Gen m a+realFloat range =+ shrink (Shrink.towardsFloat $ Range.origin range) (realFrac_ range)++-- | Generates a random fractional number in the [inclusive,exclusive) range.+--+-- /This generator does not shrink./+--+realFrac_ :: (Monad m, RealFrac a) => Range a -> Gen m a+realFrac_ range =+ generate $ \size seed ->+ let+ (x, y) =+ Range.bounds size range+ in+ realToFrac . fst $+ Seed.nextDouble (realToFrac x) (realToFrac y) seed++-- | Generates a random floating-point number in the @[inclusive,exclusive)@ range.+--+-- /This is a specialization of 'realFloat', offered for convenience./+--+float :: Monad m => Range Float -> Gen m Float+float =+ realFloat++-- | Generates a random floating-point number in the @[inclusive,exclusive)@ range.+--+-- /This is a specialization of 'realFloat', offered for convenience./+--+double :: Monad m => Range Double -> Gen m Double+double =+ realFloat++------------------------------------------------------------------------+-- Combinators - Enumeration++-- | Generates an element from an enumeration.+--+-- This generator shrinks towards the first argument.+--+-- For example:+--+-- @+-- enum \'a' \'z' :: 'Gen' 'Char'+-- @+--+enum :: (Monad m, Enum a) => a -> a -> Gen m a+enum lo hi =+ fmap toEnum . integral $+ Range.constant (fromEnum lo) (fromEnum hi)++-- | Generates a random value from a bounded enumeration.+--+-- This generator shrinks towards 'minBound'.+--+-- For example:+--+-- @+-- enumBounded :: 'Gen' 'Bool'+-- @+--+enumBounded :: (Monad m, Enum a, Bounded a) => Gen m a+enumBounded =+ enum minBound maxBound++-- | Generates a random boolean.+--+-- This generator shrinks to 'False'.+--+-- /This is a specialization of 'enumBounded', offered for convenience./+--+bool :: Monad m => Gen m Bool+bool =+ enumBounded++-- | Generates a random boolean.+--+-- /This generator does not shrink./+--+bool_ :: Monad m => Gen m Bool+bool_ =+ generate $ \_ seed ->+ (/= 0) . fst $ Seed.nextInteger 0 1 seed++------------------------------------------------------------------------+-- Combinators - Characters++-- | Generates an ASCII binit: @'0'..'1'@+--+binit :: Monad m => Gen m Char+binit =+ enum '0' '1'++-- | Generates an ASCII octit: @'0'..'7'@+--+octit :: Monad m => Gen m Char+octit =+ enum '0' '7'++-- | Generates an ASCII digit: @'0'..'9'@+--+digit :: Monad m => Gen m Char+digit =+ enum '0' '9'++-- | Generates an ASCII hexit: @'0'..'9', \'a\'..\'f\', \'A\'..\'F\'@+--+hexit :: Monad m => Gen m Char+hexit =+ -- FIXME optimize lookup, use a SmallArray or something.+ element "0123456789aAbBcCdDeEfF"++-- | Generates an ASCII lowercase letter: @\'a\'..\'z\'@+--+lower :: Monad m => Gen m Char+lower =+ enum 'a' 'z'++-- | Generates an ASCII uppercase letter: @\'A\'..\'Z\'@+--+upper :: Monad m => Gen m Char+upper =+ enum 'A' 'Z'++-- | Generates an ASCII letter: @\'a\'..\'z\', \'A\'..\'Z\'@+--+alpha :: Monad m => Gen m Char+alpha =+ -- FIXME optimize lookup, use a SmallArray or something.+ element "abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"++-- | Generates an ASCII letter or digit: @\'a\'..\'z\', \'A\'..\'Z\', \'0\'..\'9\'@+--+alphaNum :: Monad m => Gen m Char+alphaNum =+ -- FIXME optimize lookup, use a SmallArray or something.+ element "abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"++-- | Generates an ASCII character: @'\0'..'\127'@+--+ascii :: Monad m => Gen m Char+ascii =+ enum '\0' '\127'++-- | Generates a Latin-1 character: @'\0'..'\255'@+--+latin1 :: Monad m => Gen m Char+latin1 =+ enum '\0' '\255'++-- | Generates a Unicode character, excluding invalid standalone surrogates:+-- @'\0'..'\1114111' (excluding '\55296'..'\57343')@+--+unicode :: Monad m => Gen m Char+unicode =+ filter (not . isSurrogate) unicodeAll++-- | Generates a Unicode character, including invalid standalone surrogates:+-- @'\0'..'\1114111'@+--+unicodeAll :: Monad m => Gen m Char+unicodeAll =+ enumBounded++-- | Check if a character is in the surrogate category.+--+isSurrogate :: Char -> Bool+isSurrogate x =+ x >= '\55296' && x <= '\57343'++------------------------------------------------------------------------+-- Combinators - Strings++-- | Generates a string using 'Range' to determine the length.+--+-- /This is a specialization of 'list', offered for convenience./+--+string :: Monad m => Range Int -> Gen m Char -> Gen m String+string =+ list++-- | Generates a string using 'Range' to determine the length.+--+text :: Monad m => Range Int -> Gen m Char -> Gen m Text+text range =+ fmap Text.pack . string range++-- | Generates a UTF-8 encoded string, using 'Range' to determine the length.+--+utf8 :: Monad m => Range Int -> Gen m Char -> Gen m ByteString+utf8 range =+ fmap Text.encodeUtf8 . text range++-- | Generates a random 'ByteString', using 'Range' to determine the+-- length.+--+bytes :: Monad m => Range Int -> Gen m ByteString+bytes range =+ fmap ByteString.pack $+ choice [+ list range . word8 $+ Range.constant+ (fromIntegral $ Char.ord 'a')+ (fromIntegral $ Char.ord 'z')++ , list range . word8 $+ Range.constant minBound maxBound+ ]++------------------------------------------------------------------------+-- Combinators - Choice++-- | Trivial generator that always produces the same element.+--+-- /This is another name for 'pure' \/ 'return'./+constant :: Monad m => a -> Gen m a+constant =+ pure++-- | Randomly selects one of the elements in the list.+--+-- This generator shrinks towards the first element in the list.+--+-- /The input list must be non-empty./+--+element :: Monad m => [a] -> Gen m a+element = \case+ [] ->+ error "Hedgehog.Gen.element: used with empty list"+ xs -> do+ n <- integral $ Range.constant 0 (length xs - 1)+ pure $ xs !! n++-- | Randomly selects one of the generators in the list.+--+-- This generator shrinks towards the first generator in the list.+--+-- /The input list must be non-empty./+--+choice :: Monad m => [Gen m a] -> Gen m a+choice = \case+ [] ->+ error "Hedgehog.Gen.choice: used with empty list"+ xs -> do+ n <- integral $ Range.constant 0 (length xs - 1)+ xs !! n++-- | Uses a weighted distribution to randomly select one of the generators in+-- the list.+--+-- This generator shrinks towards the first generator in the list.+--+-- /The input list must be non-empty./+--+frequency :: Monad m => [(Int, Gen m a)] -> Gen m a+frequency = \case+ [] ->+ error "Hedgehog.Gen.frequency: used with empty list"+ xs0 -> do+ let+ pick n = \case+ [] ->+ error "Hedgehog.Gen.frequency/pick: used with empty list"+ (k, x) : xs ->+ if n <= k then+ x+ else+ pick (n - k) xs++ total =+ sum (fmap fst xs0)++ n <- integral $ Range.constant 1 total+ pick n xs0++-- | Modifies combinators which choose from a list of generators, like 'choice'+-- or 'frequency', so that they can be used in recursive scenarios.+--+-- This combinator modifies its target to select one of the generators in+-- either the non-recursive or the recursive list. When a selection is made+-- from the recursive list, the 'Size' is halved. When the 'Size' gets to one+-- or less, selections are no longer made from the recursive list, this+-- ensures termination.+--+-- A good example of where this might be useful is abstract syntax trees:+--+-- @+-- data Expr =+-- Var String+-- | Lam String Expr+-- | App Expr Expr+--+-- -- Assuming we have a name generator+-- genName :: 'Monad' m => 'Gen' m String+--+-- -- We can write a generator for expressions+-- genExpr :: 'Monad' m => 'Gen' m Expr+-- genExpr =+-- Gen.'recursive' Gen.'choice' [+-- -- non-recursive generators+-- Var '<$>' genName+-- ] [+-- -- recursive generators+-- Gen.'subtermM' genExpr (\x -> Lam '<$>' genName '<*>' pure x)+-- , Gen.'subterm2' genExpr genExpr App+-- ]+-- @+--+-- If we wrote the above example using only 'choice', it is likely that it+-- would fail to terminate. This is because for every call to @genExpr@,+-- there is a 2 in 3 chance that we will recurse again.+--+recursive :: ([Gen m a] -> Gen m a) -> [Gen m a] -> [Gen m a] -> Gen m a+recursive f nonrec rec =+ sized $ \n ->+ if n <= 1 then+ f nonrec+ else+ f $ nonrec ++ fmap small rec++------------------------------------------------------------------------+-- Combinators - Conditional++-- | Discards the whole generator.+--+-- /This is another name for 'empty' \/ 'mzero'./+--+discard :: Monad m => Gen m a+discard =+ mzero++-- | Generates a value that satisfies a predicate.+--+-- This is essentially:+--+-- @+-- filter p gen = 'mfilter' p gen '<|>' filter p gen+-- @+--+-- It differs from the above in that we keep some state to avoid looping+-- forever. If we trigger these limits then the whole generator is discarded.+--+filter :: Monad m => (a -> Bool) -> Gen m a -> Gen m a+filter p gen =+ let+ try k =+ if k > 100 then+ empty+ else+ mfilter p (scale (2 * k +) gen) <|> try (k + 1)+ in+ try 0++-- | Runs a 'Maybe' generator until it produces a 'Just'.+--+-- This is implemented using 'filter' and has the same caveats.+--+just :: Monad m => Gen m (Maybe a) -> Gen m a+just g = do+ mx <- filter Maybe.isJust g+ case mx of+ Just x ->+ pure x+ Nothing ->+ error "Hedgehog.Gen.just: internal error, unexpected Nothing"++------------------------------------------------------------------------+-- Combinators - Collections++-- | Generates a 'Nothing' some of the time.+--+maybe :: Monad m => Gen m a -> Gen m (Maybe a)+maybe gen =+ sized $ \n ->+ frequency [+ (2, pure Nothing)+ , (1 + fromIntegral n, Just <$> gen)+ ]++-- | Generates a list using a 'Range' to determine the length.+--+list :: Monad m => Range Int -> Gen m a -> Gen m [a]+list range gen =+ sized $ \size ->+ (traverse snd =<<) .+ mfilter (atLeast $ Range.lowerBound size range) .+ shrink Shrink.list $ do+ k <- integral_ range+ replicateM k (freeze gen)++-- | Generates a seq using a 'Range' to determine the length.+--+seq :: Monad m => Range Int -> Gen m a -> Gen m (Seq a)+seq range gen =+ Seq.fromList <$> list range gen++-- | Generates a non-empty list using a 'Range' to determine the length.+--+nonEmpty :: Monad m => Range Int -> Gen m a -> Gen m (NonEmpty a)+nonEmpty range gen = do+ xs <- list (fmap (max 1) range) gen+ case xs of+ [] ->+ error "Hedgehog.Gen.nonEmpty: internal error, generated empty list"+ _ ->+ pure $ NonEmpty.fromList xs++-- | Generates a set using a 'Range' to determine the length.+--+-- /This may fail to generate anything if the element generator/+-- /cannot produce a large enough number of unique items to satify/+-- /the required set size./+--+set :: (Monad m, Ord a) => Range Int -> Gen m a -> Gen m (Set a)+set range gen =+ fmap Map.keysSet . map range $ fmap (, ()) gen++-- | Generates a map using a 'Range' to determine the length.+--+-- /This may fail to generate anything if the keys produced by the/+-- /generator do not account for a large enough number of unique/+-- /items to satify the required map size./+--+map :: (Monad m, Ord k) => Range Int -> Gen m (k, v) -> Gen m (Map k v)+map range gen =+ sized $ \size ->+ mfilter ((>= Range.lowerBound size range) . Map.size) .+ fmap Map.fromList .+ (sequence =<<) .+ shrink Shrink.list $ do+ k <- integral_ range+ uniqueByKey k gen++-- | Generate exactly 'n' unique generators.+--+uniqueByKey :: (Monad m, Ord k) => Int -> Gen m (k, v) -> Gen m [Gen m (k, v)]+uniqueByKey n gen =+ let+ try k xs0 =+ if k > 100 then+ mzero+ else+ replicateM n (freeze gen) >>= \kvs ->+ case uniqueInsert n xs0 (fmap (first fst) kvs) of+ Left xs ->+ pure $ Map.elems xs+ Right xs ->+ try (k + 1) xs+ in+ try (0 :: Int) Map.empty++uniqueInsert :: Ord k => Int -> Map k v -> [(k, v)] -> Either (Map k v) (Map k v)+uniqueInsert n xs kvs0 =+ if Map.size xs >= n then+ Left xs+ else+ case kvs0 of+ [] ->+ Right xs+ (k, v) : kvs ->+ uniqueInsert n (Map.insertWith (\x _ -> x) k v xs) kvs++-- | Check that list contains at least a certain number of elements.+--+atLeast :: Int -> [a] -> Bool+atLeast n =+ if n == 0 then+ const True+ else+ not . null . drop (n - 1)++------------------------------------------------------------------------+-- Combinators - Subterms++data Subterms n a =+ One a+ | All (Vec n a)+ deriving (Functor, Foldable, Traversable)++data Nat =+ Z+ | S Nat++data Vec n a where+ Nil :: Vec 'Z a+ (:.) :: a -> Vec n a -> Vec ('S n) a++infixr 5 :.++deriving instance Functor (Vec n)+deriving instance Foldable (Vec n)+deriving instance Traversable (Vec n)++shrinkSubterms :: Subterms n a -> [Subterms n a]+shrinkSubterms = \case+ One _ ->+ []+ All xs ->+ fmap One $ toList xs++genSubterms :: Monad m => Vec n (Gen m a) -> Gen m (Subterms n a)+genSubterms =+ (sequence =<<) .+ shrink shrinkSubterms .+ fmap All .+ mapM (fmap snd . freeze)++fromSubterms :: Applicative m => (Vec n a -> m a) -> Subterms n a -> m a+fromSubterms f = \case+ One x ->+ pure x+ All xs ->+ f xs++-- | Constructs a generator from a number of sub-term generators.+--+-- /Shrinks to one of the sub-terms if possible./+--+subtermMVec :: Monad m => Vec n (Gen m a) -> (Vec n a -> Gen m a) -> Gen m a+subtermMVec gs f =+ fromSubterms f =<< genSubterms gs++-- | Constructs a generator from a sub-term generator.+--+-- /Shrinks to the sub-term if possible./+--+subtermM :: Monad m => Gen m a -> (a -> Gen m a) -> Gen m a+subtermM gx f =+ subtermMVec (gx :. Nil) $ \(x :. Nil) ->+ f x++-- | Constructs a generator from a sub-term generator.+--+-- /Shrinks to the sub-term if possible./+--+subterm :: Monad m => Gen m a -> (a -> a) -> Gen m a+subterm gx f =+ subtermM gx $ \x ->+ pure (f x)++-- | Constructs a generator from two sub-term generators.+--+-- /Shrinks to one of the sub-terms if possible./+--+subtermM2 :: Monad m => Gen m a -> Gen m a -> (a -> a -> Gen m a) -> Gen m a+subtermM2 gx gy f =+ subtermMVec (gx :. gy :. Nil) $ \(x :. y :. Nil) ->+ f x y++-- | Constructs a generator from two sub-term generators.+--+-- /Shrinks to one of the sub-terms if possible./+--+subterm2 :: Monad m => Gen m a -> Gen m a -> (a -> a -> a) -> Gen m a+subterm2 gx gy f =+ subtermM2 gx gy $ \x y ->+ pure (f x y)++-- | Constructs a generator from three sub-term generators.+--+-- /Shrinks to one of the sub-terms if possible./+--+subtermM3 :: Monad m => Gen m a -> Gen m a -> Gen m a -> (a -> a -> a -> Gen m a) -> Gen m a+subtermM3 gx gy gz f =+ subtermMVec (gx :. gy :. gz :. Nil) $ \(x :. y :. z :. Nil) ->+ f x y z++-- | Constructs a generator from three sub-term generators.+--+-- /Shrinks to one of the sub-terms if possible./+--+subterm3 :: Monad m => Gen m a -> Gen m a -> Gen m a -> (a -> a -> a -> a) -> Gen m a+subterm3 gx gy gz f =+ subtermM3 gx gy gz $ \x y z ->+ pure (f x y z)++------------------------------------------------------------------------+-- Combinators - Combinations & Permutations++-- | Generates a random subsequence of a list.+--+subsequence :: Monad m => [a] -> Gen m [a]+subsequence xs =+ shrink Shrink.list $ filterM (const bool_) xs++-- | Generates a random permutation of a list.+--+-- This shrinks towards the order of the list being identical to the input+-- list.+--+shuffle :: Monad m => [a] -> Gen m [a]+shuffle = \case+ [] ->+ pure []+ xs0 -> do+ n <- integral $ Range.constant 0 (length xs0 - 1)+ case splitAt n xs0 of+ (xs, y : ys) ->+ (y :) <$> shuffle (xs ++ ys)+ (_, []) ->+ error "Hedgehog.shuffle: internal error, split generated empty list"++------------------------------------------------------------------------+-- Sampling++-- | Generate a random sample of data from the a generator.+--+sample :: MonadIO m => Gen m a -> m [a]+sample gen =+ fmap (fmap nodeValue . Maybe.catMaybes) .+ replicateM 10 $ do+ seed <- liftIO Seed.random+ runMaybeT . runTree $ runGen 30 seed gen++-- | Print the value produced by a generator, and the first level of shrinks,+-- for the given size and seed.+--+-- Use 'print' to generate a value from a random seed.+--+printWith :: (MonadIO m, Show a) => Size -> Seed -> Gen m a -> m ()+printWith size seed gen = do+ Node x ss <- runTree $ renderNodes size seed gen+ liftIO $ putStrLn "=== Outcome ==="+ liftIO $ putStrLn x+ liftIO $ putStrLn "=== Shrinks ==="+ for_ ss $ \s -> do+ Node y _ <- runTree s+ liftIO $ putStrLn y++-- | Print the shrink tree produced by a generator, for the given size and+-- seed.+--+-- Use 'printTree' to generate a value from a random seed.+--+printTreeWith :: (MonadIO m, Show a) => Size -> Seed -> Gen m a -> m ()+printTreeWith size seed gen = do+ s <- Tree.render $ renderNodes size seed gen+ liftIO $ putStr s++-- | Run a generator with a random seed and print the outcome, and the first+-- level of shrinks.+--+-- @+-- Gen.print (Gen.'enum' \'a\' \'f\')+-- @+--+-- > === Outcome ===+-- > 'd'+-- > === Shrinks ===+-- > 'a'+-- > 'b'+-- > 'c'+--+print :: (MonadIO m, Show a) => Gen m a -> m ()+print gen = do+ seed <- liftIO Seed.random+ printWith 30 seed gen++-- | Run a generator with a random seed and print the resulting shrink tree.+--+-- @+-- Gen.printTree (Gen.'enum' \'a\' \'f\')+-- @+--+-- > 'd'+-- > ├╼'a'+-- > ├╼'b'+-- > │ └╼'a'+-- > └╼'c'+-- > ├╼'a'+-- > └╼'b'+-- > └╼'a'+--+-- /This may not terminate when the tree is very large./+--+printTree :: (MonadIO m, Show a) => Gen m a -> m ()+printTree gen = do+ seed <- liftIO Seed.random+ printTreeWith 30 seed gen++-- | Render a generator as a tree of strings.+--+renderNodes :: (Monad m, Show a) => Size -> Seed -> Gen m a -> Tree m String+renderNodes size seed =+ fmap (Maybe.maybe "<discard>" show) . runDiscardEffect . runGen size seed++------------------------------------------------------------------------+-- Internal++-- $internal+--+-- These functions are exported in case you need them in a pinch, but are not+-- part of the public API and may change at any time, even as part of a minor+-- update.
+ src/Hedgehog/Internal/HTraversable.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE RankNTypes #-}+module Hedgehog.Internal.HTraversable (+ HTraversable(..)+ ) where+++-- | Higher-order traversable functors.+--+class HTraversable t where+ htraverse :: Applicative f => (forall a. g a -> f (h a)) -> t g -> f (t h)
+ src/Hedgehog/Internal/Opaque.hs view
@@ -0,0 +1,25 @@+module Hedgehog.Internal.Opaque (+ Opaque(..)+ ) where+++-- | Opaque values.+--+-- Useful if you want to put something without a 'Show' instance inside+-- something which you'd like to be able to display.+--+-- For example:+--+-- @+-- data Ref v =+-- Ref (v (Opaque (IORef Int)))+-- @+--+newtype Opaque a =+ Opaque {+ unOpaque :: a+ } deriving (Eq, Ord)++instance Show (Opaque a) where+ showsPrec _ (Opaque _) =+ showString "Opaque"
src/Hedgehog/Internal/Property.hs view
@@ -46,8 +46,12 @@ , assert , (===) + , liftCatch+ , liftCatchIO , liftEither , liftExceptT++ , withCatch , withExceptT , withResourceT @@ -80,9 +84,10 @@ import Data.Semigroup (Semigroup) import Data.String (IsString) -import Hedgehog.Gen (Gen)-import qualified Hedgehog.Gen as Gen import Hedgehog.Internal.Distributive+import Hedgehog.Internal.Exception+import Hedgehog.Internal.Gen (Gen)+import qualified Hedgehog.Internal.Gen as Gen import Hedgehog.Internal.Show import Hedgehog.Internal.Source @@ -469,12 +474,39 @@ liftExceptT m = withFrozenCallStack liftEither =<< lift (runExceptT m) +-- | Fails the test if the action throws an exception.+--+-- /The benefit of using this over 'lift' is that the location of the+-- exception will be shown in the output./+--+liftCatch :: (MonadCatch m, HasCallStack) => m a -> Test m a+liftCatch m =+ withFrozenCallStack liftEither =<< lift (tryAll m)++-- | Fails the test if the action throws an exception.+--+-- /The benefit of using this over 'liftIO' is that the location of the+-- exception will be shown in the output./+--+liftCatchIO :: (MonadIO m, HasCallStack) => IO a -> Test m a+liftCatchIO m =+ withFrozenCallStack liftEither =<< liftIO (tryAll m)+ -- | Fails the test if the 'ExceptT' is 'Left', otherwise returns the value in -- the 'Right'. -- withExceptT :: (Monad m, Show x, HasCallStack) => Test (ExceptT x m) a -> Test m a withExceptT m = withFrozenCallStack liftEither =<< runExceptT (distribute m)++-- | Fails the test if the action throws an exception.+--+-- /The benefit of using this over simply letting the exception bubble up is+-- that the location of the closest 'withCatch' will be shown in the output./+--+withCatch :: (MonadCatch m, HasCallStack) => Test m a -> Test m a+withCatch m =+ withFrozenCallStack liftEither =<< tryAll m -- | Run a computation which requires resource acquisition / release. --
+ src/Hedgehog/Internal/Range.hs view
@@ -0,0 +1,461 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Hedgehog.Internal.Range (+ -- * Size+ Size(..)++ -- * Range+ , Range(..)+ , origin+ , bounds+ , lowerBound+ , upperBound++ -- * Constant+ , singleton+ , constant+ , constantFrom+ , constantBounded++ -- * Linear+ , linear+ , linearFrom+ , linearFrac+ , linearFracFrom+ , linearBounded++ -- * Exponential+ , exponential+ , exponentialFrom+ , exponentialBounded+ , exponentialFloat+ , exponentialFloatFrom++ -- * Internal+ -- $internal+ , clamp+ , scaleLinear+ , scaleLinearFrac+ , scaleExponential+ , scaleExponentialFloat+ ) where++import Data.Bifunctor (bimap)++import Prelude hiding (minimum, maximum)++-- $setup+-- >>> import Data.Int (Int8)+-- >>> let x = 3++-- | Tests are parameterized by the size of the randomly-generated data, the+-- meaning of which depends on the particular generator used.+--+newtype Size =+ Size {+ unSize :: Int+ } deriving (Eq, Ord, Num, Real, Enum, Integral)++instance Show Size where+ showsPrec p (Size x) =+ showParen (p > 10) $+ showString "Size " .+ showsPrec 11 x++instance Read Size where+ readsPrec p =+ readParen (p > 10) $ \r0 -> do+ ("Size", r1) <- lex r0+ (s, r2) <- readsPrec 11 r1+ pure (Size s, r2)++-- | A range describes the bounds of a number to generate, which may or may not+-- be dependent on a 'Size'.+--+data Range a =+ Range !a (Size -> (a, a))++instance Functor Range where+ fmap f (Range z g) =+ Range (f z) $ \sz ->+ bimap f f (g sz)++-- | Get the origin of a range. This might be the mid-point or the lower bound,+-- depending on what the range represents.+--+-- The 'bounds' of a range are scaled around this value when using the+-- 'linear' family of combinators.+--+-- When using a 'Range' to generate numbers, the shrinking function will+-- shrink towards the origin.+--+origin :: Range a -> a+origin (Range z _) =+ z++-- | Get the extents of a range, for a given size.+--+bounds :: Size -> Range a -> (a, a)+bounds sz (Range _ f) =+ f sz++-- | Get the lower bound of a range for the given size.+--+lowerBound :: Ord a => Size -> Range a -> a+lowerBound sz range =+ let+ (x, y) =+ bounds sz range+ in+ min x y++-- | Get the upper bound of a range for the given size.+--+upperBound :: Ord a => Size -> Range a -> a+upperBound sz range =+ let+ (x, y) =+ bounds sz range+ in+ max x y++-- | Construct a range which represents a constant single value.+--+-- >>> bounds x $ singleton 5+-- (5,5)+--+-- >>> origin $ singleton 5+-- 5+--+singleton :: a -> Range a+singleton x =+ Range x $ \_ -> (x, x)++-- | Construct a range which is unaffected by the size parameter.+--+-- A range from @0@ to @10@, with the origin at @0@:+--+-- >>> bounds x $ constant 0 10+-- (0,10)+--+-- >>> origin $ constant 0 10+-- 0+--+constant :: a -> a -> Range a+constant x y =+ constantFrom x x y++-- | Construct a range which is unaffected by the size parameter with a origin+-- point which may differ from the bounds.+--+-- A range from @-10@ to @10@, with the origin at @0@:+--+-- >>> bounds x $ constantFrom 0 (-10) 10+-- (-10,10)+--+-- >>> origin $ constantFrom 0 (-10) 10+-- 0+--+-- A range from @1970@ to @2100@, with the origin at @2000@:+--+-- >>> bounds x $ constantFrom 2000 1970 2100+-- (1970,2100)+--+-- >>> origin $ constantFrom 2000 1970 2100+-- 2000+--+constantFrom :: a -> a -> a -> Range a+constantFrom z x y =+ Range z $ \_ -> (x, y)++-- | Construct a range which is unaffected by the size parameter using the full+-- range of a data type.+--+-- A range from @-128@ to @127@, with the origin at @0@:+--+-- >>> bounds x (constantBounded :: Range Int8)+-- (-128,127)+--+-- >>> origin (constantBounded :: Range Int8)+-- 0+--+constantBounded :: (Bounded a, Num a) => Range a+constantBounded =+ constantFrom 0 minBound maxBound++-- | Construct a range which scales the second bound relative to the size+-- parameter.+--+-- >>> bounds 0 $ linear 0 10+-- (0,0)+--+-- >>> bounds 50 $ linear 0 10+-- (0,5)+--+-- >>> bounds 99 $ linear 0 10+-- (0,10)+--+linear :: Integral a => a -> a -> Range a+linear x y =+ linearFrom x x y++-- | Construct a range which scales the bounds relative to the size parameter.+--+-- >>> bounds 0 $ linearFrom 0 (-10) 10+-- (0,0)+--+-- >>> bounds 50 $ linearFrom 0 (-10) 20+-- (-5,10)+--+-- >>> bounds 99 $ linearFrom 0 (-10) 20+-- (-10,20)+--+linearFrom :: Integral a => a -> a -> a -> Range a+linearFrom z x y =+ Range z $ \sz ->+ let+ x_sized =+ clamp x y $ scaleLinear sz z x++ y_sized =+ clamp x y $ scaleLinear sz z y+ in+ (x_sized, y_sized)++-- | Construct a range which is scaled relative to the size parameter and uses+-- the full range of a data type.+--+-- >>> bounds 0 (linearBounded :: Range Int8)+-- (0,0)+--+-- >>> bounds 50 (linearBounded :: Range Int8)+-- (-64,64)+--+-- >>> bounds 99 (linearBounded :: Range Int8)+-- (-128,127)+--+linearBounded :: (Bounded a, Integral a) => Range a+linearBounded =+ linearFrom 0 minBound maxBound++-- | Construct a range which scales the second bound relative to the size+-- parameter.+--+-- This works the same as 'linear', but for fractional values.+--+linearFrac :: (Fractional a, Ord a) => a -> a -> Range a+linearFrac x y =+ linearFracFrom x x y++-- | Construct a range which scales the bounds relative to the size parameter.+--+-- This works the same as 'linearFrom', but for fractional values.+--+linearFracFrom :: (Fractional a, Ord a) => a -> a -> a -> Range a+linearFracFrom z x y =+ Range z $ \sz ->+ let+ x_sized =+ clamp x y $ scaleLinearFrac sz z x++ y_sized =+ clamp x y $ scaleLinearFrac sz z y+ in+ (x_sized, y_sized)++-- | Truncate a value so it stays within some range.+--+-- >>> clamp 5 10 15+-- 10+--+-- >>> clamp 5 10 0+-- 5+--+clamp :: Ord a => a -> a -> a -> a+clamp x y n =+ if x > y then+ min x (max y n)+ else+ min y (max x n)++-- | Scale an integral linearly with the size parameter.+--+scaleLinear :: Integral a => Size -> a -> a -> a+scaleLinear sz0 z0 n0 =+ let+ sz =+ max 0 (min 99 sz0)++ z =+ toInteger z0++ n =+ toInteger n0++ diff =+ ((n - z) * fromIntegral sz) `quot` 99+ in+ fromInteger $ z + diff++-- | Scale a fractional number linearly with the size parameter.+--+scaleLinearFrac :: Fractional a => Size -> a -> a -> a+scaleLinearFrac sz0 z n =+ let+ sz =+ max 0 (min 99 sz0)++ diff =+ (n - z) * (fromIntegral sz / 99)+ in+ z + diff++-- | Construct a range which scales the second bound exponentially relative to+-- the size parameter.+--+-- >>> bounds 0 $ exponential 1 512+-- (1,1)+--+-- >>> bounds 11 $ exponential 1 512+-- (1,2)+--+-- >>> bounds 22 $ exponential 1 512+-- (1,4)+--+-- >>> bounds 77 $ exponential 1 512+-- (1,128)+--+-- >>> bounds 88 $ exponential 1 512+-- (1,256)+--+-- >>> bounds 99 $ exponential 1 512+-- (1,512)+--+exponential :: Integral a => a -> a -> Range a+exponential x y =+ exponentialFrom x x y++-- | Construct a range which scales the bounds exponentially relative to the+-- size parameter.+--+-- >>> bounds 0 $ exponentialFrom 0 (-128) 512+-- (0,0)+--+-- >>> bounds 25 $ exponentialFrom 0 (-128) 512+-- (-2,4)+--+-- >>> bounds 50 $ exponentialFrom 0 (-128) 512+-- (-11,22)+--+-- >>> bounds 75 $ exponentialFrom 0 (-128) 512+-- (-39,112)+--+-- >>> bounds 99 $ exponentialFrom x (-128) 512+-- (-128,512)+--+exponentialFrom :: Integral a => a -> a -> a -> Range a+exponentialFrom z x y =+ Range z $ \sz ->+ let+ sized_x =+ clamp x y $ scaleExponential sz z x++ sized_y =+ clamp x y $ scaleExponential sz z y+ in+ (sized_x, sized_y)++-- | Construct a range which is scaled exponentially relative to the size+-- parameter and uses the full range of a data type.+--+-- >>> bounds 0 (exponentialBounded :: Range Int8)+-- (0,0)+--+-- >>> bounds 50 (exponentialBounded :: Range Int8)+-- (-11,11)+--+-- >>> bounds 99 (exponentialBounded :: Range Int8)+-- (-128,127)+--+exponentialBounded :: (Bounded a, Integral a) => Range a+exponentialBounded =+ exponentialFrom 0 minBound maxBound++-- | Construct a range which scales the second bound exponentially relative to+-- the size parameter.+--+-- This works the same as 'exponential', but for floating-point values.+--+-- >>> bounds 0 $ exponentialFloat 0 10+-- (0.0,0.0)+--+-- >>> bounds 50 $ exponentialFloat 0 10+-- (0.0,2.357035250656098)+--+-- >>> bounds 99 $ exponentialFloat 0 10+-- (0.0,10.0)+--+exponentialFloat :: (Floating a, Ord a) => a -> a -> Range a+exponentialFloat x y =+ exponentialFloatFrom x x y++-- | Construct a range which scales the bounds exponentially relative to the+-- size parameter.+--+-- This works the same as 'exponentialFrom', but for floating-point values.+--+-- >>> bounds 0 $ exponentialFloatFrom 0 (-10) 20+-- (0.0,0.0)+--+-- >>> bounds 50 $ exponentialFloatFrom 0 (-10) 20+-- (-2.357035250656098,3.6535836249197002)+--+-- >>> bounds 99 $ exponentialFloatFrom x (-10) 20+-- (-10.0,20.0)+--+exponentialFloatFrom :: (Floating a, Ord a) => a -> a -> a -> Range a+exponentialFloatFrom z x y =+ Range z $ \sz ->+ let+ sized_x =+ clamp x y $ scaleExponentialFloat sz z x++ sized_y =+ clamp x y $ scaleExponentialFloat sz z y+ in+ (sized_x, sized_y)++-- | Scale an integral exponentially with the size parameter.+--+scaleExponential :: Integral a => Size -> a -> a -> a+scaleExponential sz z0 n0 =+ let+ z =+ fromIntegral z0++ n =+ fromIntegral n0+ in+ round (scaleExponentialFloat sz z n :: Double)++-- | Scale a floating-point number exponentially with the size parameter.+--+scaleExponentialFloat :: Floating a => Size -> a -> a -> a+scaleExponentialFloat sz0 z n =+ let+ sz =+ clamp 0 99 sz0++ diff =+ (((abs (n - z) + 1) ** (realToFrac sz / 99)) - 1) * signum (n - z)+ in+ z + diff+++------------------------------------------------------------------------+-- Internal++-- $internal+--+-- These functions are exported in case you need them in a pinch, but are not+-- part of the public API and may change at any time, even as part of a minor+-- update.
src/Hedgehog/Internal/Runner.hs view
@@ -28,9 +28,8 @@ import Data.Semigroup ((<>)) -import Hedgehog.Gen (runGen)-import qualified Hedgehog.Gen as Gen import Hedgehog.Internal.Config+import Hedgehog.Internal.Gen (runGen, runDiscardEffect) import Hedgehog.Internal.Property (Group(..), GroupName(..)) import Hedgehog.Internal.Property (Property(..), PropertyConfig(..), PropertyName(..)) import Hedgehog.Internal.Property (ShrinkLimit, withTests)@@ -104,7 +103,7 @@ Left (Failure loc err mdiff) -> do let failure =- mkFailure size seed shrinks loc err mdiff w+ mkFailure size seed shrinks loc err mdiff (reverse w) updateUI $ Shrinking failure @@ -157,7 +156,7 @@ case Seed.split seed of (s0, s1) -> do node@(Node x _) <-- runTree . Gen.runDiscardEffect $ runGen size s0 (runTest test)+ runTree . runDiscardEffect $ runGen size s0 (runTest test) case x of Nothing -> loop tests (discards + 1) (size + 1) s1
+ src/Hedgehog/Internal/State.hs view
@@ -0,0 +1,474 @@+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+module Hedgehog.Internal.State (+ -- * Variables+ Var(..)+ , Symbolic(..)+ , Concrete(..)++ -- * Environment+ , Environment(..)+ , EnvironmentError(..)+ , emptyEnvironment+ , insertConcrete+ , reifyDynamic+ , reifyEnvironment+ , reify++ -- * Commands+ , Command(..)+ , Callback(..)+ , commandGenOK++ -- * Actions+ , Action(..)+ , takeVariables+ , variablesOK+ , dropInvalid+ , action+ , actions+ , execute+ , executeSequential+ ) where++import Control.Monad (when, foldM_)+import Control.Monad.Catch (MonadCatch)+import Control.Monad.Morph (hoist)+import Control.Monad.State.Class (get, put, modify)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.State (StateT, execState, evalStateT)++import Data.Dynamic (Dynamic, toDyn, fromDynamic, dynTypeRep)+import Data.Foldable (traverse_)+import Data.Functor.Classes (Eq1(..), Ord1(..), Show1(..), showsPrec1)+import Data.Map (Map)+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import Data.Set (Set)+import qualified Data.Set as Set+import Data.Typeable (Typeable, TypeRep, Proxy(..), typeRep)++import Hedgehog.Internal.Gen (Gen)+import qualified Hedgehog.Internal.Gen as Gen+import Hedgehog.Internal.HTraversable (HTraversable(..))+import Hedgehog.Internal.Property (Test, liftEither, withCatch, success)+import qualified Hedgehog.Internal.Shrink as Shrink+import Hedgehog.Internal.Source (HasCallStack, withFrozenCallStack)+import Hedgehog.Internal.Range (Range)+++-- | Symbolic variable names.+--+newtype Var =+ Var Int+ deriving (Eq, Ord, Show, Num)++-- | Symbolic values.+--+data Symbolic a where+ Symbolic :: Typeable a => Var -> Symbolic a++deriving instance Eq (Symbolic a)+deriving instance Ord (Symbolic a)++instance Show (Symbolic a) where+ showsPrec p (Symbolic x) =+ showsPrec p x++instance Show1 Symbolic where+ liftShowsPrec _ _ p (Symbolic x) =+ showsPrec p x++instance Eq1 Symbolic where+ liftEq _ (Symbolic x) (Symbolic y) =+ x == y++instance Ord1 Symbolic where+ liftCompare _ (Symbolic x) (Symbolic y) =+ compare x y++-- | Concrete values.+--+newtype Concrete a where+ Concrete :: a -> Concrete a+ deriving (Eq, Ord, Functor, Foldable, Traversable)++instance Show a => Show (Concrete a) where+ showsPrec =+ showsPrec1++instance Show1 Concrete where+ liftShowsPrec sp _ p (Concrete x) =+ sp p x++instance Eq1 Concrete where+ liftEq eq (Concrete x) (Concrete y) =+ eq x y++instance Ord1 Concrete where+ liftCompare comp (Concrete x) (Concrete y) =+ comp x y++------------------------------------------------------------------------+-- Symbolic Environment++-- | A mapping of symbolic values to concrete values.+--+newtype Environment =+ Environment {+ unEnvironment :: Map Var Dynamic+ } deriving (Show)++-- | Environment errors.+--+data EnvironmentError =+ EnvironmentValueNotFound !Var+ | EnvironmentTypeError !TypeRep !TypeRep+ deriving (Eq, Ord, Show)++-- | Create an empty environment.+--+emptyEnvironment :: Environment+emptyEnvironment =+ Environment Map.empty++-- | Insert a symbolic / concrete pairing in to the environment.+--+insertConcrete :: Symbolic a -> Concrete a -> Environment -> Environment+insertConcrete (Symbolic k) (Concrete v) =+ Environment . Map.insert k (toDyn v) . unEnvironment++-- | Cast a 'Dynamic' in to a concrete value.+--+reifyDynamic :: forall a. Typeable a => Dynamic -> Either EnvironmentError (Concrete a)+reifyDynamic dyn =+ case fromDynamic dyn of+ Nothing ->+ Left $ EnvironmentTypeError (typeRep (Proxy :: Proxy a)) (dynTypeRep dyn)+ Just x ->+ Right $ Concrete x++-- | Turns an environment in to a function for looking up a concrete value from+-- a symbolic one.+--+reifyEnvironment :: Environment -> (forall a. Symbolic a -> Either EnvironmentError (Concrete a))+reifyEnvironment (Environment vars) (Symbolic n) =+ case Map.lookup n vars of+ Nothing ->+ Left $ EnvironmentValueNotFound n+ Just dyn ->+ reifyDynamic dyn++-- | Convert a symbolic structure to a concrete one, using the provided environment.+--+reify :: HTraversable t => Environment -> t Symbolic -> Either EnvironmentError (t Concrete)+reify vars =+ htraverse (reifyEnvironment vars)++------------------------------------------------------------------------+-- Callbacks++-- | Optional command configuration.+--+data Callback input output m state =+ -- | A pre-condition for a command that must be verified before the command+ -- can be executed. This is mainly used during shrinking to ensure that it+ -- is still OK to run a command despite the fact that some previously+ -- executed commands may have been removed from the sequence.+ --+ Require (state Symbolic -> input Symbolic -> Bool)++ -- | Updates the model state, given the input and output of the command. Note+ -- that this function is polymorphic in the type of values. This is because+ -- it must work over 'Symbolic' values when we are generating actions, and+ -- 'Concrete' values when we are executing them.+ --+ | Update (forall v. Ord1 v => state v -> input v -> v output -> state v)++ -- | A post-condition for a command that must be verified for the command to+ -- be considered a success.+ --+ | Ensure (state Concrete -> input Concrete -> output -> Test m ())++callbackRequire1 ::+ state Symbolic+ -> input Symbolic+ -> Callback input output m state+ -> Bool+callbackRequire1 s i = \case+ Require f ->+ f s i+ Update _ ->+ True+ Ensure _ ->+ True++callbackUpdate1 ::+ Ord1 v+ => state v+ -> input v+ -> v output+ -> Callback input output m state+ -> state v+callbackUpdate1 s i o = \case+ Require _ ->+ s+ Update f ->+ f s i o+ Ensure _ ->+ s++callbackEnsure1 ::+ Monad m+ => state Concrete+ -> input Concrete+ -> output+ -> Callback input output m state+ -> Test m ()+callbackEnsure1 s i o = \case+ Require _ ->+ success+ Update _ ->+ success+ Ensure f ->+ f s i o++callbackRequire ::+ [Callback input output m state]+ -> state Symbolic+ -> input Symbolic+ -> Bool+callbackRequire callbacks s i =+ all (callbackRequire1 s i) callbacks++callbackUpdate ::+ Ord1 v+ => [Callback input output m state]+ -> state v+ -> input v+ -> v output+ -> state v+callbackUpdate callbacks s0 i o =+ foldl (\s -> callbackUpdate1 s i o) s0 callbacks++callbackEnsure ::+ Monad m+ => [Callback input output m state]+ -> state Concrete+ -> input Concrete+ -> output+ -> Test m ()+callbackEnsure callbacks s i o =+ traverse_ (callbackEnsure1 s i o) callbacks++------------------------------------------------------------------------++-- | The specification for the expected behaviour of an 'Action'.+--+data Command n m (state :: (* -> *) -> *) =+ forall input output.+ (HTraversable input, Show (input Symbolic), Typeable output) =>+ Command {+ -- | A generator which provides random arguments for a command. If the+ -- command cannot be executed in the current state, it should return+ -- 'Nothing'.+ --+ commandGen ::+ state Symbolic -> Maybe (Gen n (input Symbolic))++ -- | Executes a command using the arguments generated by 'commandGen'.+ --+ , commandExecute ::+ input Concrete -> Test m output++ -- | A set of callbacks which provide optional command configuration such+ -- as pre-condtions, post-conditions and state updates.+ --+ , commandCallbacks ::+ [Callback input output m state]+ }++-- | Checks that input for a command can be executed in the given state.+--+commandGenOK :: Command n m state -> state Symbolic -> Bool+commandGenOK (Command inputGen _ _) state =+ Maybe.isJust (inputGen state)++-- | An instantiation of a 'Command' which can be executed, and its effect+-- evaluated.+--+data Action m (state :: (* -> *) -> *) =+ forall input output.+ (HTraversable input, Show (input Symbolic)) =>+ Action {+ actionInput ::+ input Symbolic++ , actionOutput ::+ Symbolic output++ , actionExecute ::+ input Concrete -> Test m output++ , actionRequire ::+ state Symbolic -> input Symbolic -> Bool++ , actionUpdate ::+ forall v. Ord1 v => state v -> input v -> v output -> state v++ , actionEnsure ::+ state Concrete -> input Concrete -> output -> Test m ()+ }++instance Show (Action m state) where+ showsPrec p (Action input output _ _ _ _) =+ showParen (p > 10) $+ showsPrec 11 output .+ showString " :<- " .+ showsPrec 11 input++-- | Collects all the symbolic values in a data structure and produces a set of+-- all the variables they refer to.+--+takeVariables :: HTraversable t => t Symbolic -> Set Var+takeVariables xs =+ let+ go x@(Symbolic var) = do+ modify (Set.insert var)+ pure x+ in+ flip execState Set.empty $ htraverse go xs++-- | Checks that the symbolic values in the data structure refer only to the+-- variables in the provided set.+--+variablesOK :: HTraversable t => t Symbolic -> Set Var -> Bool+variablesOK xs allowed =+ Set.null (takeVariables xs `Set.difference` allowed)++-- | Drops invalid actions from the sequence.+--+dropInvalid :: (forall v. state v) -> [Action m state] -> [Action m state]+dropInvalid initial =+ let+ loop step@(Action input output@(Symbolic var) _execute require update _ensure) = do+ ((state0, vars0), steps0) <- get++ when (require state0 input && variablesOK input vars0) $+ let+ state =+ update state0 input output++ vars =+ Set.insert var vars0++ steps =+ steps0 ++ [step]+ in+ put ((state, vars), steps)+ in+ snd . flip execState ((initial, Set.empty), []) . traverse_ loop++-- | Generates a single action from a set of possible commands.+--+action ::+ (Monad n, Monad m)+ => [Command n m state]+ -> Gen (StateT (state Symbolic, Var) n) (Action m state)+action commands =+ Gen.just $ do+ (state, var) <- get++ Command mgenInput exec callbacks <-+ Gen.element $ filter (\c -> commandGenOK c state) commands++ input <-+ case mgenInput state of+ Nothing ->+ error "genCommand: internal error, tried to use generator with invalid state."+ Just g ->+ hoist lift g++ if not $ callbackRequire callbacks state input then+ pure Nothing++ else do+ let+ output =+ Symbolic var++ put (callbackUpdate callbacks state input output, var + 1)++ pure . Just $+ Action input output exec+ (callbackRequire callbacks)+ (callbackUpdate callbacks)+ (callbackEnsure callbacks)++-- | Generates a sequence of actions from an initial model state and set of commands.+--+actions ::+ (Monad n, Monad m)+ => Range Int+ -> (forall v. state v)+ -> [Command n m state]+ -> Gen n [Action m state]+actions range initial =+ fmap (dropInvalid initial) .+ Gen.shrink Shrink.list .+ hoist (flip evalStateT (initial, 0)) .+ Gen.list range .+ action++-- | Executes a single action in the given evironment.+--+execute ::+ (HasCallStack, Monad m)+ => (state Concrete, Environment)+ -> Action m state+ -> Test m (state Concrete, Environment)+execute (state0, env0) (Action sinput soutput exec _require update ensure) =+ withFrozenCallStack $ do+ input <- liftEither $ reify env0 sinput+ output <- exec input++ let+ coutput =+ Concrete output++ state =+ update state0 input coutput++ env =+ insertConcrete soutput coutput env0++ ensure state input output++ pure (state, env)++-- | Executes a list of actions sequentially, verifying that all+-- post-conditions are met and no exceptions are thrown.+--+-- To generate a sequence of actions to execute, see the+-- 'Hedgehog.Gen.actions' combinator in the "Hedgehog.Gen" module.+--+executeSequential ::+ forall m state.+ (HasCallStack, MonadCatch m)+ => (forall v. state v)+ -> [Action m state]+ -> Test m ()+executeSequential initial commands =+ withFrozenCallStack $+ withCatch (foldM_ execute (initial, emptyEnvironment) commands)
src/Hedgehog/Range.hs view
@@ -4,7 +4,7 @@ Size(..) -- * Range- , Range(..)+ , Range , origin , bounds , lowerBound@@ -29,433 +29,6 @@ , exponentialBounded , exponentialFloat , exponentialFloatFrom-- -- * Internal- -- $internal- , clamp- , scaleLinear- , scaleLinearFrac- , scaleExponential- , scaleExponentialFloat ) where -import Data.Bifunctor (bimap)--import Prelude hiding (minimum, maximum)---- $setup--- >>> import Data.Int (Int8)--- >>> let x = 3---- | Tests are parameterized by the size of the randomly-generated data, the--- meaning of which depends on the particular generator used.----newtype Size =- Size {- unSize :: Int- } deriving (Eq, Ord, Num, Real, Enum, Integral)--instance Show Size where- showsPrec p (Size x) =- showParen (p > 10) $- showString "Size " .- showsPrec 11 x--instance Read Size where- readsPrec p =- readParen (p > 10) $ \r0 -> do- ("Size", r1) <- lex r0- (s, r2) <- readsPrec 11 r1- pure (Size s, r2)---- | A range describes the bounds of a number to generate, which may or may not--- be dependent on a 'Size'.----data Range a =- Range !a (Size -> (a, a))--instance Functor Range where- fmap f (Range z g) =- Range (f z) $ \sz ->- bimap f f (g sz)---- | Get the origin of a range. This might be the mid-point or the lower bound,--- depending on what the range represents.------ The 'bounds' of a range are scaled around this value when using the--- 'linear' family of combinators.------ When using a 'Range' to generate numbers, the shrinking function will--- shrink towards the origin.----origin :: Range a -> a-origin (Range z _) =- z---- | Get the extents of a range, for a given size.----bounds :: Size -> Range a -> (a, a)-bounds sz (Range _ f) =- f sz---- | Get the lower bound of a range for the given size.----lowerBound :: Ord a => Size -> Range a -> a-lowerBound sz range =- let- (x, y) =- bounds sz range- in- min x y---- | Get the upper bound of a range for the given size.----upperBound :: Ord a => Size -> Range a -> a-upperBound sz range =- let- (x, y) =- bounds sz range- in- max x y---- | Construct a range which represents a constant single value.------ >>> bounds x $ singleton 5--- (5,5)------ >>> origin $ singleton 5--- 5----singleton :: a -> Range a-singleton x =- Range x $ \_ -> (x, x)---- | Construct a range which is unaffected by the size parameter.------ A range from @0@ to @10@, with the origin at @0@:------ >>> bounds x $ constant 0 10--- (0,10)------ >>> origin $ constant 0 10--- 0----constant :: a -> a -> Range a-constant x y =- constantFrom x x y---- | Construct a range which is unaffected by the size parameter with a origin--- point which may differ from the bounds.------ A range from @-10@ to @10@, with the origin at @0@:------ >>> bounds x $ constantFrom 0 (-10) 10--- (-10,10)------ >>> origin $ constantFrom 0 (-10) 10--- 0------ A range from @1970@ to @2100@, with the origin at @2000@:------ >>> bounds x $ constantFrom 2000 1970 2100--- (1970,2100)------ >>> origin $ constantFrom 2000 1970 2100--- 2000----constantFrom :: a -> a -> a -> Range a-constantFrom z x y =- Range z $ \_ -> (x, y)---- | Construct a range which is unaffected by the size parameter using the full--- range of a data type.------ A range from @-128@ to @127@, with the origin at @0@:------ >>> bounds x (constantBounded :: Range Int8)--- (-128,127)------ >>> origin (constantBounded :: Range Int8)--- 0----constantBounded :: (Bounded a, Num a) => Range a-constantBounded =- constantFrom 0 minBound maxBound---- | Construct a range which scales the second bound relative to the size--- parameter.------ >>> bounds 0 $ linear 0 10--- (0,0)------ >>> bounds 50 $ linear 0 10--- (0,5)------ >>> bounds 99 $ linear 0 10--- (0,10)----linear :: Integral a => a -> a -> Range a-linear x y =- linearFrom x x y---- | Construct a range which scales the bounds relative to the size parameter.------ >>> bounds 0 $ linearFrom 0 (-10) 10--- (0,0)------ >>> bounds 50 $ linearFrom 0 (-10) 20--- (-5,10)------ >>> bounds 99 $ linearFrom 0 (-10) 20--- (-10,20)----linearFrom :: Integral a => a -> a -> a -> Range a-linearFrom z x y =- Range z $ \sz ->- let- x_sized =- clamp x y $ scaleLinear sz z x-- y_sized =- clamp x y $ scaleLinear sz z y- in- (x_sized, y_sized)---- | Construct a range which is scaled relative to the size parameter and uses--- the full range of a data type.------ >>> bounds 0 (linearBounded :: Range Int8)--- (0,0)------ >>> bounds 50 (linearBounded :: Range Int8)--- (-64,64)------ >>> bounds 99 (linearBounded :: Range Int8)--- (-128,127)----linearBounded :: (Bounded a, Integral a) => Range a-linearBounded =- linearFrom 0 minBound maxBound---- | Construct a range which scales the second bound relative to the size--- parameter.------ This works the same as 'linear', but for fractional values.----linearFrac :: (Fractional a, Ord a) => a -> a -> Range a-linearFrac x y =- linearFracFrom x x y---- | Construct a range which scales the bounds relative to the size parameter.------ This works the same as 'linearFrom', but for fractional values.----linearFracFrom :: (Fractional a, Ord a) => a -> a -> a -> Range a-linearFracFrom z x y =- Range z $ \sz ->- let- x_sized =- clamp x y $ scaleLinearFrac sz z x-- y_sized =- clamp x y $ scaleLinearFrac sz z y- in- (x_sized, y_sized)---- | Truncate a value so it stays within some range.------ >>> clamp 5 10 15--- 10------ >>> clamp 5 10 0--- 5----clamp :: Ord a => a -> a -> a -> a-clamp x y n =- if x > y then- min x (max y n)- else- min y (max x n)---- | Scale an integral linearly with the size parameter.----scaleLinear :: Integral a => Size -> a -> a -> a-scaleLinear sz0 z0 n0 =- let- sz =- max 0 (min 99 sz0)-- z =- toInteger z0-- n =- toInteger n0-- diff =- ((n - z) * fromIntegral sz) `quot` 99- in- fromInteger $ z + diff---- | Scale a fractional number linearly with the size parameter.----scaleLinearFrac :: Fractional a => Size -> a -> a -> a-scaleLinearFrac sz0 z n =- let- sz =- max 0 (min 99 sz0)-- diff =- (n - z) * (fromIntegral sz / 99)- in- z + diff---- | Construct a range which scales the second bound exponentially relative to--- the size parameter.------ >>> bounds 0 $ exponential 1 512--- (1,1)------ >>> bounds 11 $ exponential 1 512--- (1,2)------ >>> bounds 22 $ exponential 1 512--- (1,4)------ >>> bounds 77 $ exponential 1 512--- (1,128)------ >>> bounds 88 $ exponential 1 512--- (1,256)------ >>> bounds 99 $ exponential 1 512--- (1,512)----exponential :: Integral a => a -> a -> Range a-exponential x y =- exponentialFrom x x y---- | Construct a range which scales the bounds exponentially relative to the--- size parameter.------ >>> bounds 0 $ exponentialFrom 0 (-128) 512--- (0,0)------ >>> bounds 25 $ exponentialFrom 0 (-128) 512--- (-2,4)------ >>> bounds 50 $ exponentialFrom 0 (-128) 512--- (-11,22)------ >>> bounds 75 $ exponentialFrom 0 (-128) 512--- (-39,112)------ >>> bounds 99 $ exponentialFrom x (-128) 512--- (-128,512)----exponentialFrom :: Integral a => a -> a -> a -> Range a-exponentialFrom z x y =- Range z $ \sz ->- let- sized_x =- clamp x y $ scaleExponential sz z x-- sized_y =- clamp x y $ scaleExponential sz z y- in- (sized_x, sized_y)---- | Construct a range which is scaled exponentially relative to the size--- parameter and uses the full range of a data type.------ >>> bounds 0 (exponentialBounded :: Range Int8)--- (0,0)------ >>> bounds 50 (exponentialBounded :: Range Int8)--- (-11,11)------ >>> bounds 99 (exponentialBounded :: Range Int8)--- (-128,127)----exponentialBounded :: (Bounded a, Integral a) => Range a-exponentialBounded =- exponentialFrom 0 minBound maxBound---- | Construct a range which scales the second bound exponentially relative to--- the size parameter.------ This works the same as 'exponential', but for floating-point values.------ >>> bounds 0 $ exponentialFloat 0 10--- (0.0,0.0)------ >>> bounds 50 $ exponentialFloat 0 10--- (0.0,2.357035250656098)------ >>> bounds 99 $ exponentialFloat 0 10--- (0.0,10.0)----exponentialFloat :: (Floating a, Ord a) => a -> a -> Range a-exponentialFloat x y =- exponentialFloatFrom x x y---- | Construct a range which scales the bounds exponentially relative to the--- size parameter.------ This works the same as 'exponentialFrom', but for floating-point values.------ >>> bounds 0 $ exponentialFloatFrom 0 (-10) 20--- (0.0,0.0)------ >>> bounds 50 $ exponentialFloatFrom 0 (-10) 20--- (-2.357035250656098,3.6535836249197002)------ >>> bounds 99 $ exponentialFloatFrom x (-10) 20--- (-10.0,20.0)----exponentialFloatFrom :: (Floating a, Ord a) => a -> a -> a -> Range a-exponentialFloatFrom z x y =- Range z $ \sz ->- let- sized_x =- clamp x y $ scaleExponentialFloat sz z x-- sized_y =- clamp x y $ scaleExponentialFloat sz z y- in- (sized_x, sized_y)---- | Scale an integral exponentially with the size parameter.----scaleExponential :: Integral a => Size -> a -> a -> a-scaleExponential sz z0 n0 =- let- z =- fromIntegral z0-- n =- fromIntegral n0- in- round (scaleExponentialFloat sz z n :: Double)---- | Scale a floating-point number exponentially with the size parameter.----scaleExponentialFloat :: Floating a => Size -> a -> a -> a-scaleExponentialFloat sz0 z n =- let- sz =- clamp 0 99 sz0-- diff =- (((abs (n - z) + 1) ** (realToFrac sz / 99)) - 1) * signum (n - z)- in- z + diff------------------------------------------------------------------------------ Internal---- $internal------ These functions are exported in case you need them in a pinch, but are not--- part of the public API and may change at any time, even as part of a minor--- update.+import Hedgehog.Internal.Range