registry-hedgehog 0.2.1.1 → 0.3.0.0
raw patch · 18 files changed
+822/−573 lines, 18 files
Files
- registry-hedgehog.cabal +4/−3
- src/Data/Registry/Hedgehog.hs +107/−110
- src/Data/Registry/Hedgehog/TH.hs +7/−9
- src/Data/Registry/Internal/Hedgehog.hs +48/−52
- src/Data/Registry/Internal/TH.hs +15/−19
- src/Test/Tasty/HedgehogTest.hs +290/−0
- src/Test/Tasty/Hedgehogx.hs +80/−151
- test/Test/Data/Registry/Company.hs +22/−19
- test/Test/Data/Registry/Generators.hs +25/−25
- test/Test/Data/Registry/HedgehogSpec.hs +94/−63
- test/Test/Tutorial/DataModel.hs +22/−19
- test/Test/Tutorial/Exercise1.hs +15/−15
- test/Test/Tutorial/Exercise2.hs +21/−21
- test/Test/Tutorial/Exercise3.hs +13/−13
- test/Test/Tutorial/Exercise4.hs +13/−13
- test/Test/Tutorial/Exercise5.hs +23/−19
- test/Test/Tutorial/Exercise6.hs +20/−19
- test/test.hs +3/−3
registry-hedgehog.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.2.+-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: 8e9ef405c71b1f7fffa4735a082d31c503420e8345fd885ef0408becd6689cc4+-- hash: 5172e9a2dee2ae8064a8ddd51dbad2923b92ace8e8141381ba7b40be413c7bdb name: registry-hedgehog-version: 0.2.1.1+version: 0.3.0.0 synopsis: utilities to work with Hedgehog generators and `registry` description: This library provides some functions to extract generators from a "Registry" and make stateful modifications of that Registry to precisely control the generation of data category: Control@@ -26,6 +26,7 @@ Data.Registry.Hedgehog.TH Data.Registry.Internal.Hedgehog Data.Registry.Internal.TH+ Test.Tasty.HedgehogTest Test.Tasty.Hedgehogx other-modules: Paths_registry_hedgehog
src/Data/Registry/Hedgehog.hs view
@@ -1,167 +1,163 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DataKinds #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE PartialTypeSignatures #-}-{-# LANGUAGE UndecidableInstances #-}--module Data.Registry.Hedgehog (- -- creation / tweaking functions- GenIO-, Chooser (..)-, forallS-, forAllT -- re-export of forAllT for convenience purpose since we are working in GenIO-, filterGenS-, genFun-, genVal-, genWith-, modifyGenS-, setGen-, setGenIO-, setGenS-, specializeGen-, specializeGenIO-, specializeGenS-, tweakGen-, tweakGenS-, makeNonEmpty-, makeNonEmptyS---- combinators to compose different types of generators-, eitherOf-, hashMapOf-, listOf-, listOfMinMax-, mapOf-, maybeOf-, nonEmptyMapOf-, nonEmptyOf-, pairOf-, setOf-, tripleOf---- cycling values-, choiceChooser-, chooseOne-, setCycleChooser-, setCycleChooserS---- making distinct values-, distinct-, setDistinct-, setDistinctFor-, setDistinctForS-, setDistinctS+{-# LANGUAGE UndecidableInstances #-} --- sampling for GenIO generators-, sampleIO-) where+module Data.Registry.Hedgehog+ ( -- creation / tweaking functions+ GenIO,+ Chooser (..),+ forallS,+ forAllT, -- re-export of forAllT for convenience purpose since we are working in GenIO+ filterGenS,+ genFun,+ genVal,+ genWith,+ modifyGenS,+ setGen,+ setGenIO,+ setGenS,+ specializeGen,+ specializeGenIO,+ specializeGenS,+ tweakGen,+ tweakGenS,+ makeNonEmpty,+ makeNonEmptyS,+ -- combinators to compose different types of generators+ eitherOf,+ hashMapOf,+ listOf,+ listOfMinMax,+ mapOf,+ maybeOf,+ nonEmptyMapOf,+ nonEmptyOf,+ pairOf,+ setOf,+ tripleOf,+ -- cycling values+ choiceChooser,+ chooseOne,+ setCycleChooser,+ setCycleChooserS,+ -- making distinct values+ distinct,+ setDistinct,+ setDistinctFor,+ setDistinctForS,+ setDistinctS,+ -- sampling for GenIO generators+ sampleIO,+ )+where -import Control.Monad.Morph-import Data.HashMap.Strict as HashMap (HashMap, fromList)-import Data.IORef-import Data.List.NonEmpty hiding (cycle, nonEmpty, (!!))-import Data.Map as Map (fromList)-import Data.Maybe as Maybe-import Data.Registry-import Data.Registry.Internal.Hedgehog-import Data.Registry.Internal.Types-import Data.Set as Set (fromList)-import Hedgehog-import Hedgehog.Gen as Gen-import Hedgehog.Internal.Gen as Gen-import Hedgehog.Internal.Property (forAllT)-import Hedgehog.Range-import Protolude as P-import System.IO.Unsafe+import Control.Monad.Morph+import Data.HashMap.Strict as HashMap (HashMap, fromList)+import Data.IORef+import Data.List.NonEmpty hiding (cycle, nonEmpty, (!!))+import Data.Map as Map (fromList)+import Data.Maybe as Maybe+import Data.Registry+import Data.Registry.Internal.Hedgehog+import Data.Registry.Internal.Types+import Data.Set as Set (fromList)+import Hedgehog+import Hedgehog.Gen as Gen+import Hedgehog.Internal.Property (forAllT)+import Hedgehog.Range+import Protolude as P+import System.IO.Unsafe -- * CREATION / TWEAKING OF REGISTRY GENERATORS -- | Create a GenIO a for a given constructor of type a-genFun :: forall a b . (ApplyVariadic GenIO a b, Typeable a, Typeable b) => a -> Typed b+genFun :: forall a b. (ApplyVariadic GenIO a b, Typeable a, Typeable b) => a -> Typed b genFun = funTo @GenIO -- | Lift a Gen a into GenIO a to be added to a registry-genVal :: forall a . (Typeable a) => Gen a -> Typed (GenIO a)+genVal :: forall a. (Typeable a) => Gen a -> Typed (GenIO a) genVal g = fun (liftGen g) -- | Extract a generator from a registry -- We use makeUnsafe assuming that the registry has been checked before-genWith :: forall a ins out . (Typeable a) => Registry ins out -> GenIO a+genWith :: forall a ins out. (Typeable a) => Registry ins out -> GenIO a genWith = makeUnsafe @(GenIO a) -- | Modify the value of a generator in a given registry-tweakGen :: forall a ins out . (Typeable a) => (a -> a) -> Registry ins out -> Registry ins out+tweakGen :: forall a ins out. (Typeable a) => (a -> a) -> Registry ins out -> Registry ins out tweakGen f = tweakUnsafe @(GenIO a) (\genA -> f <$> genA) -- | Modify the registry for a given generator in a State monad-tweakGenS :: forall a m ins out . (Typeable a, MonadState (Registry ins out) m) => (a -> a) -> m ()+tweakGenS :: forall a m ins out. (Typeable a, MonadState (Registry ins out) m) => (a -> a) -> m () tweakGenS f = modify (tweakGen f) -- | Set a specific generator on the registry the value of a generator in a given registry-setGen :: forall a ins out . (Typeable a) => Gen a -> Registry ins out -> Registry ins out+setGen :: forall a ins out. (Typeable a) => Gen a -> Registry ins out -> Registry ins out setGen = setGenIO . liftGen -setGenIO :: forall a ins out . (Typeable a) => GenIO a -> Registry ins out -> Registry ins out+setGenIO :: forall a ins out. (Typeable a) => GenIO a -> Registry ins out -> Registry ins out setGenIO genA = tweakUnsafe @(GenIO a) (const genA) -- | Set a specific generator on the registry the value of a generator in a given registry in a State monad-setGenS :: forall a m ins out . (Typeable a, MonadState (Registry ins out) m) => Gen a -> m ()+setGenS :: forall a m ins out. (Typeable a, MonadState (Registry ins out) m) => Gen a -> m () setGenS genA = modify (setGen genA) -- | Specialize a generator in a given context-specializeGen :: forall a b ins out . (Typeable a, Typeable b, Contains (GenIO a) out) => Gen b -> Registry ins out -> Registry ins out+specializeGen :: forall a b ins out. (Typeable a, Typeable b, Contains (GenIO a) out) => Gen b -> Registry ins out -> Registry ins out specializeGen g = specializeGenIO @a (liftGen g) -- | Specialize a generator in a given context-specializeGenIO :: forall a b ins out . (Typeable a, Typeable b, Contains (GenIO a) out) => GenIO b -> Registry ins out -> Registry ins out+specializeGenIO :: forall a b ins out. (Typeable a, Typeable b, Contains (GenIO a) out) => GenIO b -> Registry ins out -> Registry ins out specializeGenIO = specialize @(GenIO a) -- | Specialize a generator in a given context-specializeGenS :: forall a b m ins out . (Typeable a, Typeable b, Contains (GenIO a) out, MonadState (Registry ins out) m) => Gen b -> m ()+specializeGenS :: forall a b m ins out. (Typeable a, Typeable b, Contains (GenIO a) out, MonadState (Registry ins out) m) => Gen b -> m () specializeGenS g = modify (specializeGen @a @b g) -- | Modify a generator-modifyGenS :: forall a ins out . (Typeable a) => (GenIO a -> GenIO a) -> PropertyT (StateT (Registry ins out) IO) ()+modifyGenS :: forall a ins out. (Typeable a) => (GenIO a -> GenIO a) -> PropertyT (StateT (Registry ins out) IO) () modifyGenS f = modify (tweakUnsafe @(GenIO a) f) -- | Filter a generator-filterGenS :: forall a ins out . (Typeable a) => (a -> Bool) -> PropertyT (StateT (Registry ins out) IO) ()+filterGenS :: forall a ins out. (Typeable a) => (a -> Bool) -> PropertyT (StateT (Registry ins out) IO) () filterGenS = modifyGenS . Gen.filterT -- | Get a value generated from one of the generators in the registry and modify the registry -- using a state monad-forallS :: forall a m out . (Typeable a, Show a, MonadIO m) => PropertyT (StateT (Registry _ out) m) a+forallS :: forall a m out. (Typeable a, Show a, MonadIO m) => PropertyT (StateT (Registry _ out) m) a forallS = do r <- P.lift $ get withFrozenCallStack $ hoist liftIO $ forAllT (genWith @a r) -- | Make sure there is always one element of a given type in a list of elements-makeNonEmpty :: forall a ins out . (Typeable a) => Registry ins out -> Registry ins out+makeNonEmpty :: forall a ins out. (Typeable a) => Registry ins out -> Registry ins out makeNonEmpty r = -- extract a generator for one element only let genA = genWith @a r- -- add that element in front of a list of generated elements- in tweakUnsafe @(GenIO [a]) (\genAs -> (:) <$> genA <*> genAs) r+ in -- add that element in front of a list of generated elements+ tweakUnsafe @(GenIO [a]) (\genAs -> (:) <$> genA <*> genAs) r -- | Make sure there is always one element of a given type in a list of elements in a State monad-makeNonEmptyS :: forall a m ins out . (Typeable a, MonadState (Registry ins out) m) => m ()+makeNonEmptyS :: forall a m ins out. (Typeable a, MonadState (Registry ins out) m) => m () makeNonEmptyS = modify (makeNonEmpty @a) -- * CONTAINERS COMBINATORS -- | Create a generator for a pair-pairOf :: forall a b . GenIO a -> GenIO b -> GenIO (a, b)+pairOf :: forall a b. GenIO a -> GenIO b -> GenIO (a, b) pairOf ga gb = (,) <$> ga <*> gb -- | Create a generator for a triple-tripleOf :: forall a b c . GenIO a -> GenIO b -> GenIO c -> GenIO (a, b, c)+tripleOf :: forall a b c. GenIO a -> GenIO b -> GenIO c -> GenIO (a, b, c) tripleOf ga gb gc = (,,) <$> ga <*> gb <*> gc -- | Create a default generator for a small list of elements-listOf :: forall a . GenIO a -> GenIO [a]+listOf :: forall a. GenIO a -> GenIO [a] listOf = Gen.list (linear 0 10) -- | Create a default generator for a list of elements of min elements and max elements-listOfMinMax :: forall a . Int -> Int -> GenIO a -> GenIO [a]+listOfMinMax :: forall a. Int -> Int -> GenIO a -> GenIO [a] listOfMinMax min' max' = Gen.list (linear min' max') -- | Create a default generator for a small non-empty list of elements@@ -169,27 +165,27 @@ nonEmptyOf = Gen.nonEmpty (linear 1 10) -- | Create a default generator for a Maybe, choosing evenly between Nothing and Just-maybeOf :: forall a . GenIO a -> GenIO (Maybe a)+maybeOf :: forall a. GenIO a -> GenIO (Maybe a) maybeOf genA = choice [pure Nothing, Just <$> genA] -- | Create a default generator for a Either, choosing evenly between Left and Right-eitherOf :: forall a b . GenIO a -> GenIO b -> GenIO (Either a b)+eitherOf :: forall a b. GenIO a -> GenIO b -> GenIO (Either a b) eitherOf genA genB = choice [Left <$> genA, Right <$> genB] -- | Create a default generator for a small set of elements-setOf :: forall a . (Ord a) => GenIO a -> GenIO (Set a)+setOf :: forall a. (Ord a) => GenIO a -> GenIO (Set a) setOf = fmap Set.fromList . listOf -- | Create a default generator for map of key/values-mapOf :: forall k v . (Ord k) => GenIO k -> GenIO v -> GenIO (Map k v)+mapOf :: forall k v. (Ord k) => GenIO k -> GenIO v -> GenIO (Map k v) mapOf gk gv = Map.fromList <$> listOf (pairOf gk gv) -- | Create a default generator for HashMap of key/values-hashMapOf :: forall k v . (Ord k, Hashable k) => GenIO k -> GenIO v -> GenIO (HashMap k v)+hashMapOf :: forall k v. (Ord k, Hashable k) => GenIO k -> GenIO v -> GenIO (HashMap k v) hashMapOf gk gv = HashMap.fromList <$> listOf (pairOf gk gv) -- | Create a default generator for a small non-empty map of elements-nonEmptyMapOf :: forall k v . (Ord k) => GenIO k -> GenIO v -> GenIO (Map k v)+nonEmptyMapOf :: forall k v. (Ord k) => GenIO k -> GenIO v -> GenIO (Map k v) nonEmptyMapOf gk gv = do h <- pairOf gk gv t <- listOf (pairOf gk gv)@@ -201,17 +197,18 @@ -- | Set a cycling chooser for a specific data type {-# NOINLINE setCycleChooser #-}-setCycleChooser :: forall a ins out . (Typeable a, Contains (GenIO a) out) => Registry ins out -> Registry ins out+setCycleChooser :: forall a ins out. (Typeable a, Contains (GenIO a) out) => Registry ins out -> Registry ins out setCycleChooser r = unsafePerformIO $ do c <- cycleChooser pure $ specializeValTo @GenIO @(GenIO a) c r -- | Set a cycling chooser for a specific data type {-# NOINLINE setCycleChooserS #-}-setCycleChooserS :: forall a m ins out . (Typeable a, Contains (GenIO a) out, MonadState (Registry ins out) m, MonadIO m) => m ()+setCycleChooserS :: forall a m ins out. (Typeable a, Contains (GenIO a) out, MonadState (Registry ins out) m, MonadIO m) => m () setCycleChooserS = let c = unsafePerformIO cycleChooser- in do r <- get+ in do+ r <- get let r' = specializeValTo @GenIO @(GenIO a) c r put r' @@ -219,30 +216,30 @@ -- | Generate distinct values for a specific data type {-# NOINLINE setDistinct #-}-setDistinct :: forall a ins out . (Eq a, Typeable a, Contains (GenIO a) out) => Registry ins out -> Registry ins out+setDistinct :: forall a ins out. (Eq a, Typeable a, Contains (GenIO a) out) => Registry ins out -> Registry ins out setDistinct = setDistinctWithRef @a (unsafePerformIO $ newIORef []) -setDistinctWithRef :: forall a ins out . (Eq a, Typeable a, Contains (GenIO a) out) => IORef [a] -> Registry ins out -> Registry ins out+setDistinctWithRef :: forall a ins out. (Eq a, Typeable a, Contains (GenIO a) out) => IORef [a] -> Registry ins out -> Registry ins out setDistinctWithRef ref r = setGenIO (distinctWith ref (makeFast @(GenIO a) r)) r -- | Generate distinct values for a specific data type {-# NOINLINE setDistinctS #-}-setDistinctS :: forall a m ins out . (Eq a, Typeable a, Contains (GenIO a) out, MonadState (Registry ins out) m, MonadIO m) => m ()+setDistinctS :: forall a m ins out. (Eq a, Typeable a, Contains (GenIO a) out, MonadState (Registry ins out) m, MonadIO m) => m () setDistinctS = let ref = unsafePerformIO $ newIORef []- in modify (setDistinctWithRef @a ref)+ in modify (setDistinctWithRef @a ref) -- | Generate distinct values for a specific data type, when used inside another data type {-# NOINLINE setDistinctFor #-}-setDistinctFor :: forall a b ins out . (Typeable a, Contains (GenIO a) out, Eq b, Typeable b, Contains (GenIO b) out) => Registry ins out -> Registry ins out+setDistinctFor :: forall a b ins out. (Typeable a, Contains (GenIO a) out, Eq b, Typeable b, Contains (GenIO b) out) => Registry ins out -> Registry ins out setDistinctFor = setDistinctForWithRef @a @b (unsafePerformIO $ newIORef []) -setDistinctForWithRef :: forall a b ins out . (Typeable a, Contains (GenIO a) out, Eq b, Typeable b, Contains (GenIO b) out) => IORef [b] -> Registry ins out -> Registry ins out+setDistinctForWithRef :: forall a b ins out. (Typeable a, Contains (GenIO a) out, Eq b, Typeable b, Contains (GenIO b) out) => IORef [b] -> Registry ins out -> Registry ins out setDistinctForWithRef ref r = specializeGenIO @a (distinctWith ref (makeFast @(GenIO b) r)) r -- | Generate distinct values for a specific data type, when used inside another data type {-# NOINLINE setDistinctForS #-}-setDistinctForS :: forall a b m ins out . (Typeable a, Contains (GenIO a) out, Eq b, Typeable b, Contains (GenIO b) out, MonadState (Registry ins out) m, MonadIO m) => m ()+setDistinctForS :: forall a b m ins out. (Typeable a, Contains (GenIO a) out, Eq b, Typeable b, Contains (GenIO b) out, MonadState (Registry ins out) m, MonadIO m) => m () setDistinctForS = let ref = unsafePerformIO $ newIORef []- in modify (setDistinctForWithRef @a @b ref)+ in modify (setDistinctForWithRef @a @b ref)
src/Data/Registry/Hedgehog/TH.hs view
@@ -1,14 +1,14 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} module Data.Registry.Hedgehog.TH where -import Control.Monad.Fail (fail)-import Data.Registry.Internal.TH-import Language.Haskell.TH-import Language.Haskell.TH.Syntax-import Protolude+import Control.Monad.Fail (fail)+import Data.Registry.Internal.TH+import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import Protolude -- | Make a registry containing generators for an ADT -- We want to generate the following@@ -19,7 +19,6 @@ -- -- genEmployeeStatus :: GenIO Chooser -> GenIO (Tag "permanent" EmployeeStatus) -> GenIO (Tag "temporary" EmployeeStatus) -> GenIO EmployeeStatus -- genEmployeeStatus chooser g1 g2 = chooseOne chooser [fmap unTagg1, fmap unTag g2]--- makeGenerators :: Name -> ExpQ makeGenerators genType = do info <- reify genType@@ -28,7 +27,6 @@ selector <- makeSelectGenerator name constructors generators <- traverse makeConstructorGenerator constructors assembleGeneratorsToRegistry selector generators- other -> do qReport True ("can only create generators for an ADT, got: " <> show other) fail "generators creation failed"
src/Data/Registry/Internal/Hedgehog.hs view
@@ -1,35 +1,33 @@-{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE PartialTypeSignatures #-} -module Data.Registry.Internal.Hedgehog (- GenIO-, Chooser (..)---- cycling values-, cycleWith-, chooseOne-, choiceChooser-, cycleChooser---- making distinct values-, distinct-, distinctWith---- utilities-, liftGen-, sampleIO-) where+module Data.Registry.Internal.Hedgehog+ ( GenIO,+ Chooser (..),+ -- cycling values+ cycleWith,+ chooseOne,+ choiceChooser,+ cycleChooser,+ -- making distinct values+ distinct,+ distinctWith,+ -- utilities+ liftGen,+ sampleIO,+ )+where -import Control.Monad.Morph-import Data.IORef-import Data.Maybe as Maybe-import Hedgehog-import Hedgehog.Gen as Gen-import Hedgehog.Internal.Gen as Gen-import Hedgehog.Internal.Seed as Seed (random)-import Hedgehog.Internal.Tree as Tree (NodeT (..), runTreeT)-import Prelude (show, (!!))-import Protolude as P+import Control.Monad.Morph+import Data.IORef+import Data.Maybe as Maybe+import Hedgehog+import Hedgehog.Gen as Gen+import Hedgehog.Internal.Gen as Gen+import Hedgehog.Internal.Seed as Seed (random)+import Hedgehog.Internal.Tree as Tree (NodeT (..), runTreeT)+import Protolude as P+import Prelude (show, (!!)) -- | All the generators we use are lifted into GenIO to allow some generators to be stateful type GenIO = GenT IO@@ -49,21 +47,21 @@ -- | Chooser for randomly selecting a generator choiceChooser :: Chooser-choiceChooser = Chooser { chooserType = "choice", pickOne = pure . Gen.choice }+choiceChooser = Chooser {chooserType = "choice", pickOne = pure . Gen.choice} -- | Chooser for deterministically choosing elements in a list -- by cycling over them, which requires to maintain some state about the last position cycleChooser :: IO Chooser cycleChooser = do ref <- newIORef 0- pure $ Chooser { chooserType = "cycle", pickOne = cycleWith ref }+ pure $ Chooser {chooserType = "cycle", pickOne = cycleWith ref} -- | A "chooser" strategy -- The type can be used to debug specializations-data Chooser = Chooser {- chooserType :: Text-, pickOne :: forall a . [GenIO a] -> IO (GenIO a)-}+data Chooser = Chooser+ { chooserType :: Text,+ pickOne :: forall a. [GenIO a] -> IO (GenIO a)+ } instance Show Chooser where show c = toS (chooserType c)@@ -74,8 +72,8 @@ n <- readIORef ref modifyIORef ref increment pure (gs !! n)-- where increment i = if i == P.length gs - 1 then 0 else i + 1+ where+ increment i = if i == P.length gs - 1 then 0 else i + 1 -- * MAKING DISTINCT VALUES @@ -91,7 +89,7 @@ distinctWith ref g = GenT $ \size seed -> do as <- liftIO $ readIORef ref a <- runGenT size seed $ (Gen.filterT (not . flip elem as)) g- liftIO $ writeIORef ref (a:as)+ liftIO $ writeIORef ref (a : as) pure a -- * UTILITIES@@ -99,17 +97,15 @@ -- | Sample GenIO values sampleIO :: GenIO a -> IO a sampleIO gen =- let- loop n =- if n <= 0 then- panic "Hedgehog.Gen.sample: too many discards, could not generate a sample"- else do- seed <- Seed.random- NodeT r _ <- runTreeT $ evalGenT 30 seed gen- case r of- Nothing ->- loop (n - 1)- Just a ->- pure a- in- loop (100 :: Int)+ let loop n =+ if n <= 0+ then panic "Hedgehog.Gen.sample: too many discards, could not generate a sample"+ else do+ seed <- Seed.random+ NodeT r _ <- runTreeT $ evalGenT 30 seed gen+ case r of+ Nothing ->+ loop (n - 1)+ Just a ->+ pure a+ in loop (100 :: Int)
src/Data/Registry/Internal/TH.hs view
@@ -1,30 +1,28 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} module Data.Registry.Internal.TH where -import Control.Monad.Fail (fail)-import Data.Registry.Internal.Hedgehog-import Data.Text (splitOn, toLower)-import Language.Haskell.TH-import Language.Haskell.TH.Syntax-import Prelude (last)-import Protolude hiding (Type)+import Control.Monad.Fail (fail)+import Data.Registry.Internal.Hedgehog+import Data.Text (splitOn, toLower)+import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import Protolude hiding (Type)+import Prelude (last) -- | Create a generator for selecting between constructors of an ADT -- One parameter is a GenIO Chooser in order to be able to later on -- switch the selection strategy makeSelectGenerator :: Name -> [Con] -> ExpQ makeSelectGenerator name constructors = do- chooserParam <- [p| (chooser :: GenIO Chooser) |]+ chooserParam <- [p|(chooser :: GenIO Chooser)|] otherParams <- traverse (parameterFor name) constructors untaggedGenerators <- traverse untagGenerator constructors expression <- appE (appE (varE (mkName "chooseOne")) (varE (mkName "chooser"))) (pure $ ListE untaggedGenerators) pure $ LamE (chooserParam : otherParams) expression- where- -- | Create a parameters for the selection function parameterFor :: Name -> Con -> Q Pat parameterFor typeName constructor = do constructorParam <- constructorParameterName constructor@@ -36,7 +34,7 @@ -- AppE (AppTypeE (VarE Data.Registry.Lift.tag) (LitT (StrTyLit "permanent"))) (ConE Test.Data.Registry.Generators.Permanent) makeConstructorGenerator :: Con -> ExpQ makeConstructorGenerator constructor = do- constructorTag <- tagName constructor+ constructorTag <- tagName constructor constructorType <- nameOf constructor appE (appTypeE (varE (mkName "tag")) (litT (strTyLit (show constructorTag)))) (conE constructorType) @@ -67,7 +65,7 @@ -- | The name of a given constructor nameOf :: Con -> Q Name nameOf (NormalC n _) = pure n-nameOf (RecC n _) = pure n+nameOf (RecC n _) = pure n nameOf other = do qReport True ("we can only create generators for normal constructors and records, got: " <> show other) fail "generators creation failed"@@ -75,7 +73,7 @@ -- | The list of types necessary to create a given constructor typesOf :: Con -> Q [Type] typesOf (NormalC _ types) = pure (snd <$> types)-typesOf (RecC _ types) = pure $ (\(_,_,t) -> t) <$> types+typesOf (RecC _ types) = pure $ (\(_, _, t) -> t) <$> types typesOf other = do qReport True ("we can only create generators for normal constructors and records, got: " <> show other) fail "generators creation failed"@@ -86,12 +84,10 @@ assembleGeneratorsToRegistry :: Exp -> [Exp] -> ExpQ assembleGeneratorsToRegistry _ [] = fail "generators creation failed"- assembleGeneratorsToRegistry selectorGenerator [g] = let r = appendExpressions (genFunOf (pure g)) (funOf (pure selectorGenerator))- in appendExpressions r (genFunOf (varE (mkName "choiceChooser")))--assembleGeneratorsToRegistry selectorGenerator (g:gs) =+ in appendExpressions r (genFunOf (varE (mkName "choiceChooser")))+assembleGeneratorsToRegistry selectorGenerator (g : gs) = appendExpressions (genFunOf (pure g)) (assembleGeneratorsToRegistry selectorGenerator gs) appendExpressions :: ExpQ -> ExpQ -> ExpQ
+ src/Test/Tasty/HedgehogTest.hs view
@@ -0,0 +1,290 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE IncoherentInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++-- |+--+-- This redefines the HedgehogTest from tasty-hedgehog to display+-- color during the reporting+module Test.Tasty.HedgehogTest+ ( HedgehogTest (..),+ HedgehogTestLimit (..),+ HedgehogDiscardLimit (..),+ HedgehogShrinkLimit (..),+ HedgehogShrinkRetries (..),+ HedgehogReplay (..),+ HedgehogShowReplay (..),+ ModuleName (..),+ testProperty,+ groupByModuleName,+ getModuleName,+ )+where++import Data.MultiMap hiding (foldr, size)+import GHC.Stack+import Hedgehog hiding (test, (===))+import Hedgehog.Internal.Config (UseColor, detectColor)+import Hedgehog.Internal.Property+import Hedgehog.Internal.Report as Hedgehog+import Hedgehog.Internal.Runner as Hedgehog+import Hedgehog.Internal.Seed as Seed+import Protolude as P hiding (empty, toList, unwords, words)+import qualified Protolude as P+import Test.Tasty as Tasty+import Test.Tasty.Options as Tasty+import Test.Tasty.Providers as Tasty+import Test.Tasty.Runners as Tasty+ ( TestTree (..),+ foldSingle,+ foldTestTree,+ trivialFold,+ )+import Prelude (String, unwords, words)++-- | Hedgehog Property as a Tasty Test+data HedgehogTest = HedgehogTest Tasty.TestName Property+ deriving (Typeable)++-- | Create a 'Test' from a Hedgehog property+testProperty :: Tasty.TestName -> Property -> Tasty.TestTree+testProperty name prop = singleTest name (HedgehogTest name prop)++instance Tasty.IsTest HedgehogTest where+ testOptions =+ return+ [ Tasty.Option (Proxy :: Proxy HedgehogReplay),+ Tasty.Option (Proxy :: Proxy HedgehogShowReplay),+ Tasty.Option (Proxy :: Proxy HedgehogTestLimit),+ Tasty.Option (Proxy :: Proxy HedgehogDiscardLimit),+ Tasty.Option (Proxy :: Proxy HedgehogShrinkLimit),+ Tasty.Option (Proxy :: Proxy HedgehogShrinkRetries)+ ]++ run opts (HedgehogTest name (Property pConfig pTest)) yieldProgress = do+ useColor <- detectColor+ let HedgehogReplay replay = lookupOption opts+ HedgehogTestLimit mTests = lookupOption opts+ HedgehogDiscardLimit mDiscards = lookupOption opts+ HedgehogShrinkLimit mShrinks = lookupOption opts+ HedgehogShrinkRetries mRetries = lookupOption opts+ showReplay = lookupOption opts+ config =+ PropertyConfig+ (fromMaybe (propertyDiscardLimit pConfig) mDiscards)+ (fromMaybe (propertyShrinkLimit pConfig) mShrinks)+ (fromMaybe (propertyShrinkRetries pConfig) mRetries)+ (NoConfidenceTermination $ fromMaybe (propertyTestLimit pConfig) mTests)+ randSeed <- Seed.random+ -- if we just run one test we choose a high size (knowing that the max size is 99)+ -- if the test fails we can turn it to a prop and let the shrinking process find a+ -- smaller counter-example+ let minSize = if propertyTestLimit config == 1 then 50 else 0+ let size = P.maybe minSize fst replay+ seed = P.maybe randSeed snd replay+ report <- checkReport config size seed pTest (yieldProgress . reportToProgress config)+ let resultFn =+ if reportStatus report == OK+ then testPassed+ else testFailed+ out <- reportOutput showReplay useColor name report+ return $ resultFn out++reportToProgress ::+ PropertyConfig ->+ Report Hedgehog.Progress ->+ Tasty.Progress+reportToProgress config (Report testsDone _ _ status) =+ let TestLimit testLimit = propertyTestLimit config+ ShrinkLimit shrinkLimit = propertyShrinkLimit config+ ratio x y = 1.0 * fromIntegral x / fromIntegral y+ in -- TODO add details for tests run / discarded / shrunk+ case status of+ Running ->+ Tasty.Progress "Running" (ratio testsDone testLimit)+ Shrinking fr ->+ Tasty.Progress "Shrinking" (ratio (failureShrinks fr) shrinkLimit)++reportOutput ::+ HedgehogShowReplay ->+ UseColor ->+ String ->+ Report Hedgehog.Result ->+ IO String+reportOutput (HedgehogShowReplay showReplay) useColor name report = do+ s <- renderResult useColor (Just (PropertyName name)) report+ pure $ case reportStatus report of+ Failed fr ->+ let size = failureSize fr+ seed = failureSeed fr+ replayStr =+ if showReplay+ then+ " --hedgehog-replay \""+ ++ show size+ ++ " "+ ++ show seed+ ++ "\""+ else ""+ in s ++ replayStr ++ "\n"+ GaveUp ->+ s+ OK ->+ -- do not report hedgehog successes because they are redundant with the Tasty report+ -- except if there is coverage information+ if not . P.null . P.toList . coverageLabels . reportCoverage $ report+ then s+ else ""++propertyTestLimit :: PropertyConfig -> TestLimit+propertyTestLimit =+ let getTestLimit (EarlyTermination _ tests) = tests+ getTestLimit (NoEarlyTermination _ tests) = tests+ getTestLimit (NoConfidenceTermination tests) = tests+ in getTestLimit . propertyTerminationCriteria++-- * OPTIONS DEFINITIONS++-- | The replay token to use for replaying a previous test run+newtype HedgehogReplay = HedgehogReplay (Maybe (Size, Seed))+ deriving (Typeable)++instance IsOption HedgehogReplay where+ defaultValue = HedgehogReplay Nothing++ parseValue v = HedgehogReplay . Just <$> replay+ where+ -- Reads a replay token in the form "{size} {seed}"+ replay = (,) <$> safeRead (unwords size) <*> safeRead (unwords seed)+ (size, seed) = splitAt 2 $ words v++ optionName = return "hedgehog-replay"++ optionHelp = return "Replay token to use for replaying a previous test run"++-- | If a test case fails, show a replay token for replaying tests+newtype HedgehogShowReplay = HedgehogShowReplay Bool+ deriving (Typeable)++instance IsOption HedgehogShowReplay where+ defaultValue = HedgehogShowReplay True++ parseValue = fmap HedgehogShowReplay . safeRead++ optionName = return "hedgehog-show-replay"++ optionHelp = return "Show a replay token for replaying tests"++-- | The number of successful test cases required before Hedgehog will pass a test+newtype HedgehogTestLimit = HedgehogTestLimit (Maybe TestLimit)+ deriving (Eq, Ord, Show, Typeable)++instance IsOption HedgehogTestLimit where+ defaultValue = HedgehogTestLimit Nothing++ parseValue = fmap (HedgehogTestLimit . Just . TestLimit) . safeRead++ optionName = return "hedgehog-tests"++ optionHelp = return "Number of successful test cases required before Hedgehog will pass a test"++-- | The number of discarded cases allowed before Hedgehog will fail a test+newtype HedgehogDiscardLimit = HedgehogDiscardLimit (Maybe DiscardLimit)+ deriving (Eq, Ord, Show, Typeable)++instance IsOption HedgehogDiscardLimit where+ defaultValue = HedgehogDiscardLimit Nothing++ parseValue = fmap (HedgehogDiscardLimit . Just . DiscardLimit) . safeRead++ optionName = return "hedgehog-discards"++ optionHelp = return "Number of discarded cases allowed before Hedgehog will fail a test"++-- | The number of shrinks allowed before Hedgehog will fail a test+newtype HedgehogShrinkLimit = HedgehogShrinkLimit (Maybe ShrinkLimit)+ deriving (Eq, Ord, Show, Typeable)++instance IsOption HedgehogShrinkLimit where+ defaultValue = HedgehogShrinkLimit Nothing++ parseValue = fmap (HedgehogShrinkLimit . Just . ShrinkLimit) . safeRead++ optionName = return "hedgehog-shrinks"++ optionHelp = return "Number of shrinks allowed before Hedgehog will fail a test"++-- | The number of times to re-run a test during shrinking+newtype HedgehogShrinkRetries = HedgehogShrinkRetries (Maybe ShrinkRetries)+ deriving (Eq, Ord, Show, Typeable)++instance IsOption HedgehogShrinkRetries where+ defaultValue = HedgehogShrinkRetries Nothing++ parseValue = fmap (HedgehogShrinkRetries . Just . ShrinkRetries) . safeRead++ optionName = return "hedgehog-retries"++ optionHelp = return "Number of times to re-run a test during shrinking"++-- * GROUPING++-- | This allows the discovery of Hedgehog properties and their grouping by module name+-- in the test report.+-- Extract the ModuleName option value for a given test and+-- group all the tests with that option into the same test group+groupByModuleName :: TestTree -> TestTree+groupByModuleName testTree =+ let grouped =+ assocs $+ foldTestTree+ ( trivialFold+ { foldSingle = \os n t ->+ let (ModuleName aModuleName) = lookupOption os :: ModuleName+ in insert (toS aModuleName) (setOptionSet os $ singleTest n t) empty+ }+ )+ mempty+ testTree+ in TestGroup "All" (uncurry TestGroup <$> grouped)++-- | Option describing the current module name+newtype ModuleName = ModuleName Text deriving (Eq, Show)++-- | This option is not used on the command line, it is just used to annotate test groups+instance IsOption ModuleName where+ defaultValue = ModuleName "root"+ parseValue = fmap ModuleName . safeRead+ optionName = pure "module-name"+ optionHelp = pure "internal option used to group tests into the same module"+ optionCLParser = mkFlagCLParser mempty (ModuleName "root")++instance (Ord k) => Semigroup (MultiMap k v) where+ (<>) m1 m2 = fromList (toList m1 <> toList m2)++instance (Ord k) => Monoid (MultiMap k v) where+ mempty = empty+ mappend = (<>)++-- | This is unfortunate. Due to the API for `foldTestTree` in Tasty+-- giving back the current `OptionSet` applicable to a single test+-- it is not possible to re-set those option values on that test+-- without listing them exhaustively. This means+-- that if other options are set on tests in that file, they need to be+-- added in that function+setOptionSet :: OptionSet -> TestTree -> TestTree+setOptionSet os =+ localOption (lookupOption os :: HedgehogTestLimit)+ . localOption (lookupOption os :: HedgehogShrinkLimit)+ . localOption (lookupOption os :: HedgehogReplay)++-- | Return the module name of the current callstack+getModuleName :: HasCallStack => Prelude.String+getModuleName =+ case getCallStack callStack of+ ((_, loc) : _) -> srcLocModule loc+ _ -> "root"
src/Test/Tasty/Hedgehogx.hs view
@@ -1,47 +1,58 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} {- This module unifies property based testing with Hedgehog and one-off tests. -}-module Test.Tasty.Hedgehogx (- module Hedgehog-, module Tasty-, gotException-, groupByModuleName-, minTestsOk-, mustBe-, noShrink-, prop-, run-, runOnly-, test-, withSeed-, (===)-) where+module Test.Tasty.Hedgehogx+ ( module Hedgehog,+ module Tasty,+ module Test.Tasty.HedgehogTest,+ -- * Tests definition+ prop,+ test,+ -- * Tests settings+ minTestsOk,+ noShrink,+ withSeed,+ -- * Running tests+ run,+ runOnly,+ -- * Assertions+ gotException,+ -- * Display+ printDifference,+ display,+ )+where -import Data.Maybe (fromJust)-import Data.MultiMap hiding (foldr)-import GHC.Stack-import Hedgehog as Hedgehog hiding (test, (===))-import qualified Hedgehog as Hedgehog ((===))-import Hedgehog.Gen as Hedgehog hiding (discard, print)-import Prelude (String)-import Protolude hiding (SrcLoc, empty, toList, (.&.))-import System.Environment-import Test.Tasty as Tasty-import Test.Tasty.Hedgehog as Tasty-import Test.Tasty.Options as Tasty-import Test.Tasty.Providers as Tasty (singleTest)-import Test.Tasty.Runners as Tasty (TestTree (..), foldSingle,- foldTestTree, trivialFold)+import qualified Data.Text as T+import GHC.Stack+import Hedgehog hiding (test)+import Hedgehog.Gen as Hedgehog hiding (discard, print)+import Hedgehog.Internal.Config (UseColor (EnableColor))+import Hedgehog.Internal.Property (Coverage (..), Diff (..), DiscardCount (..), ShrinkCount (..), TestCount (..))+import Hedgehog.Internal.Report+import Hedgehog.Internal.Show (mkValue, valueDiff)+import Protolude hiding (SrcLoc, empty, toList, (.&.))+import System.Environment+import Test.Tasty as Tasty+import Test.Tasty.HedgehogTest+import Test.Tasty.Options as Tasty+import Test.Tasty.Providers as Tasty (singleTest)+import Test.Tasty.Runners as Tasty+ ( TestTree (..),+ foldSingle,+ foldTestTree,+ trivialFold,+ )+import Prelude (String) -- * TESTS AND PROPERTIES @@ -49,141 +60,59 @@ prop :: HasCallStack => TestName -> PropertyT IO () -> TestTree prop name p = let aModuleName = getModuleName- in withFrozenCallStack . localOption (ModuleName (toS aModuleName)) $- testProperty name (Hedgehog.property p)+ in withFrozenCallStack . localOption (ModuleName (toS aModuleName)) $+ testProperty name (Hedgehog.property p) -- | Create a Tasty test from a Hedgehog property called only once test :: HasCallStack => TestName -> PropertyT IO () -> TestTree test name p = withFrozenCallStack (minTestsOk 1 . noShrink $ prop name p) --- * ASSERTIONS---- | Assert that an exception is thrown-gotException :: forall a . (HasCallStack, Show a) => a -> PropertyT IO ()-gotException a = withFrozenCallStack $ do- res <- liftIO (try (evaluate a) :: IO (Either SomeException a))- case res of- Left _ -> assert True- Right _ -> annotateShow ("excepted an exception" :: Text) >> assert False----- | Redefinition of Hedgehog's === operator to add better source file information------ It adds a link to the source file which is conforms to--- output of GHC.Exception. See makeSourceLink--- It also displays the actual and expected values in a compact form before--- the pretty-printed form provided by Hedgehog-infix 4 ===-(===) :: (MonadTest m, Eq a, Show a, HasCallStack) => a -> a -> m ()-actual === expected = withFrozenCallStack $ do- displayActualAndExpectedValues actual expected- actual Hedgehog.=== expected---- | An equality assertion which does not try to do a smart diffs for cases--- where the output is too large.-mustBe :: (MonadTest m, Eq a, Show a, HasCallStack) => a -> a -> m ()-actual `mustBe` expected = do- ok <- eval (actual == expected)- if ok then- success- else withFrozenCallStack $ do- displayActualAndExpectedValues actual expected- failure---- | Display actual and expected values as a footnote, using the Show a instance-displayActualAndExpectedValues :: (Show a, MonadTest m, HasCallStack) => a -> a -> m ()-displayActualAndExpectedValues actual expected =- withFrozenCallStack $ do- footnote makeSourceLink- footnote "\n"- footnote $ "Expected\n" <> (show expected)- footnote "\n"- footnote $ "Actual\n" <> (show actual)---- | This function creates a link to the source file which is conforms to--- the output of GHC.Exception and thus can be navigated to with some text editors (like emacs)--- by specifying a regular expression like--- (".*error, called at \\(.*\\.hs\\):\\([0-9]+\\):\\([0-9]+\\) in .*" 1 2 3 2 1)--- (" +\\(.*\\.hs\\):\\([0-9]+\\):$" 1 2 nil 2 1)-makeSourceLink :: (HasCallStack) => String-makeSourceLink =- case getCallStack callStack of- [] -> "FAIL!"- (f, SrcLoc {..}) : _ ->- f ++ " error, called at " ++- foldr (++) ""- [ srcLocFile, ":"- , show srcLocStartLine, ":"- , show srcLocStartCol, " in "- , srcLocPackage, ":", srcLocModule- ]----- * SETTINGS+-- * SETTING TEST OPTIONS --- | Set a mininum number of tests to be successful on a property+-- | Set the minimum number of tests which must be successful for a property to pass minTestsOk :: Int -> TestTree -> TestTree minTestsOk n = localOption (HedgehogTestLimit (Just (toEnum n :: TestLimit))) --- | Don't shrink failures+-- | Do not shrink failures noShrink :: TestTree -> TestTree noShrink = localOption (HedgehogShrinkLimit (Just (0 :: ShrinkLimit))) --- | Run a property with a specify seed. You can copy and paste the exact string--- which Hedgehog outputs when there is a failure+-- | Execute a property with a specific seed withSeed :: Prelude.String -> TestTree -> TestTree-withSeed seed = localOption (fromJust (parseValue seed :: Maybe HedgehogReplay))---- * GROUPING---- | This allows the discovery of Hedgehog properties and their grouping by module name--- in the test report.--- Extract the ModuleName option value for a given test and--- group all the tests with that option into the same test group-groupByModuleName :: TestTree -> TestTree-groupByModuleName testTree =- let grouped = assocs $ foldTestTree (trivialFold { foldSingle = \os n t ->- let (ModuleName aModuleName) = lookupOption os :: ModuleName- in insert (toS aModuleName) (setOptionSet os $ singleTest n t) empty- }) mempty testTree- in TestGroup "All" (uncurry TestGroup <$> grouped)+withSeed seed tree =+ case parseValue seed of+ Nothing -> prop ("cannot parse seed " <> seed) failure+ Just (s :: HedgehogReplay) -> localOption s tree -instance (Ord k) => Semigroup (MultiMap k v) where- (<>) m1 m2 = fromList (toList m1 <> toList m2)+-- * ASSERTIONS -instance (Ord k) => Monoid (MultiMap k v) where- mempty = empty- mappend = (<>)+-- | Assert that an exception is thrown+gotException :: forall a. (HasCallStack, Show a) => a -> PropertyT IO ()+gotException a = withFrozenCallStack $ do+ res <- liftIO (try (evaluate a) :: IO (Either SomeException a))+ case res of+ Left _ -> assert True+ Right _ -> annotateShow ("excepted an exception" :: Text) >> assert False --- | This is unfortunate. Due to the API for `foldTestTree` in Tasty--- giving back the current `OptionSet` applicable to a single test--- it is not possible to re-set those option values on that test--- without listing them exhaustively. This means--- that if other options are set on tests in that file, they need to be--- added in that function-setOptionSet :: OptionSet -> TestTree -> TestTree-setOptionSet os =- localOption (lookupOption os :: HedgehogTestLimit) .- localOption (lookupOption os :: HedgehogShrinkLimit) .- localOption (lookupOption os :: HedgehogReplay)+-- * REPORTING --- | Return the module name of the current callstack-getModuleName :: HasCallStack => Prelude.String-getModuleName =- case getCallStack callStack of- ((_, loc):_) -> srcLocModule loc- _ -> "root"+printDifference :: (MonadIO m, Show a, Show b, HasCallStack) => a -> b -> m ()+printDifference actual expected = withFrozenCallStack $ do+ let failureReport = mkFailure (Size 0) (Seed 0 0) (ShrinkCount 0) Nothing Nothing "" (failureDifference actual expected) []+ report <- renderResult EnableColor Nothing (Report (TestCount 0) (DiscardCount 0) (Coverage mempty) (Failed failureReport))+ putText (T.unlines . drop 3 . T.lines . toS $ report) --- | Option describing the current module name-newtype ModuleName = ModuleName Text deriving (Eq, Show)+failureDifference :: (Show a, Show b, HasCallStack) => a -> b -> Maybe Diff+failureDifference x y = withFrozenCallStack $+ case valueDiff <$> mkValue x <*> mkValue y of+ Nothing ->+ Nothing+ Just d ->+ withFrozenCallStack $+ Just $ Diff "━━━ Failed (" "- lhs" ") (" "+ rhs" ") ━━━" d --- | This option is not used on the command line, it is just used to annotate test groups-instance IsOption ModuleName where- defaultValue = ModuleName "root"- parseValue = fmap ModuleName . safeRead- optionName = pure "module-name"- optionHelp = pure "internal option used to group tests into the same module"- optionCLParser = mkFlagCLParser mempty (ModuleName "root")+display :: (Show a, Monad m, HasCallStack) => a -> PropertyT m a+display a = withFrozenCallStack (annotateShow a $> a) -- * GHCi run functions
test/Test/Data/Registry/Company.hs view
@@ -1,27 +1,30 @@ module Test.Data.Registry.Company where -import Protolude+import Protolude -- Some complex nested data-data Company = Company {- companyName :: Text-, departments :: [Department]-} deriving (Eq, Show)+data Company = Company+ { companyName :: Text,+ departments :: [Department]+ }+ deriving (Eq, Show) -data Department = Department {- departmentName :: Text-, employees :: [Employee]-} deriving (Eq, Show)+data Department = Department+ { departmentName :: Text,+ employees :: [Employee]+ }+ deriving (Eq, Show) -data Employee = Employee {- employeeName :: Text-, employeeStatus :: EmployeeStatus-, salary :: Int-, bonus :: Maybe Int-} deriving (Eq, Show)+data Employee = Employee+ { employeeName :: Text,+ employeeStatus :: EmployeeStatus,+ salary :: Int,+ bonus :: Maybe Int+ }+ deriving (Eq, Show) -- | Note that this is an ADT with several constructors-data EmployeeStatus =- Permanent- | Temporary Int -- number of days- deriving (Eq, Show)+data EmployeeStatus+ = Permanent+ | Temporary Int -- number of days+ deriving (Eq, Show)
test/Test/Data/Registry/Generators.hs view
@@ -1,35 +1,35 @@-{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE PartialTypeSignatures #-}-{-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} module Test.Data.Registry.Generators where -import Data.Registry-import Data.Registry.Hedgehog-import Data.Registry.Hedgehog.TH-import Data.Registry.TH-import Hedgehog.Gen as Gen hiding (print)-import Hedgehog.Internal.Gen hiding (print)-import Hedgehog.Range-import Protolude hiding (list)-import Test.Data.Registry.Company-import Test.Tasty.Hedgehogx+import Data.Registry+import Data.Registry.Hedgehog+import Data.Registry.Hedgehog.TH+import Data.Registry.TH+import Hedgehog.Gen as Gen hiding (print)+import Hedgehog.Internal.Gen hiding (print)+import Hedgehog.Range+import Protolude hiding (list)+import Test.Data.Registry.Company+import Test.Tasty.Hedgehogx registry =- genFun Company- <: genFun Department- <: genFun Employee- -- we can generate data for different constructors in an ADT with some Template Haskell- <: $(makeGenerators ''EmployeeStatus)- -- we can generate Lists or Maybe of elements- <: fun (listOf @Department)- <: fun (listOf @Employee)- <: fun (maybeOf @Int)- <: genVal genInt- <: genVal genText+ genFun Company+ <: genFun Department+ <: genFun Employee+ -- we can generate data for different constructors in an ADT with some Template Haskell+ <: $(makeGenerators ''EmployeeStatus)+ -- we can generate Lists or Maybe of elements+ <: fun (listOf @Department)+ <: fun (listOf @Employee)+ <: fun (maybeOf @Int)+ <: genVal genInt+ <: genVal genText genInt :: Gen Int genInt = integral (linear 1 3)@@ -46,5 +46,5 @@ generators = $(checkRegistry 'registry) -- | We create a forall function using all the generators-forall :: forall a . _ => PropertyT IO a+forall :: forall a. _ => PropertyT IO a forall = withFrozenCallStack $ forAllT (genWith @a generators)
test/Test/Data/Registry/HedgehogSpec.hs view
@@ -1,29 +1,54 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE PartialTypeSignatures #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PartialTypeSignatures #-} {-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} module Test.Data.Registry.HedgehogSpec where -import Control.Monad.Morph (hoist)-import Data.IORef-import Data.Registry-import Data.Registry.Hedgehog-import Hedgehog.Internal.Gen hiding (print)-import Hedgehog.Range-import Hedgehog.Gen as Gen-import Protolude hiding (list)-import System.IO.Unsafe-import Test.Data.Registry.Company-import Test.Data.Registry.Generators-import Test.Tasty.Hedgehogx-import Hedgehog.Internal.Seed as Seed (random)-import Hedgehog.Internal.Tree as Tree (NodeT (..), runTreeT)-+import Control.Monad.Morph (hoist)+import Data.IORef+import Data.Registry+import Data.Registry.Hedgehog import qualified Data.Text as T import qualified Data.Text.IO as T+import Hedgehog.Gen as Gen+import Hedgehog.Internal.Gen hiding (print)+import Hedgehog.Internal.Seed as Seed (random)+import Hedgehog.Internal.Tree as Tree (NodeT (..), runTreeT)+import Hedgehog.Range+import Protolude+ ( Applicative (pure),+ Bool (True),+ Eq,+ Foldable (length),+ IO,+ Int,+ Maybe (Just, Nothing),+ Monad ((>>)),+ MonadIO (..),+ MonadState (get, put),+ Num ((+), (-)),+ Ord ((<=), (>=)),+ Show,+ State,+ Text,+ evalState,+ flip,+ head,+ lift,+ panic,+ ($),+ (.),+ (<$>),+ )+import System.IO.Unsafe+import Test.Data.Registry.Company+import Test.Data.Registry.Generators+import Test.Tasty.Hedgehogx+ -- * This specification shows the usage of several features of this library+ -- First of all you will notice that if you run `stack test` -- all the properties of this file will be grouped under the Test.Data.Registry.HedgehogSpec test group @@ -68,59 +93,67 @@ -- Let's create some registry modifiers to constrain the generation setOneDepartment = addFunS $ listOfMinMax @Department 1 1-setOneEmployee = addFunS $ listOfMinMax @Employee 1 1++setOneEmployee = addFunS $ listOfMinMax @Employee 1 1+ setSmallCompany = setOneEmployee >> setOneDepartment test_small_company =- prop "a small company has just one department and one employee" $ runS generators $ do- setSmallCompany- company <- forallS @Company- length (departments company) === 1- let Just d = head $ departments company- length (employees d) === 1+ prop "a small company has just one department and one employee" $+ runS generators $ do+ setSmallCompany+ company <- forallS @Company+ length (departments company) === 1+ let Just d = head $ departments company+ length (employees d) === 1 -- * We can also specialize some generators in a given context+ -- For example we might want to generate shorter department names even -- if Department is using Text values. To do this we specialize the Text -- generator in the context of a Gen Department genDepartmentName = T.take 5 . T.toUpper <$> genText+ setDepartmentName = specializeGenS @Department genDepartmentName test_with_better_department_name = noShrink $- prop "a department must have a short capitalized name" $ runS generators $ do- setSmallCompany- setDepartmentName- company <- forallS @Company+ prop "a department must have a short capitalized name" $+ runS generators $ do+ setSmallCompany+ setDepartmentName+ company <- forallS @Company - -- uncomment to print the department names and inspect them- -- print company- let Just d = head $ departments company- (T.length (departmentName d) <= 5) === True+ -- uncomment to print the department names and inspect them+ -- print company+ let Just d = head $ departments company+ (T.length (departmentName d) <= 5) === True -- * It would be also very nice to have stateful generation where we can cycle+ -- across different constructors for a given data type test_cycle_constructors =- prop "we can cycle deterministically across all the constructors of a data type" $ runS generators $ do- setCycleChooserS @EmployeeStatus- -- uncomment to check- -- collect =<< forallS @EmployeeStatus- success+ prop "we can cycle deterministically across all the constructors of a data type" $+ runS generators $ do+ setCycleChooserS @EmployeeStatus+ -- uncomment to check+ -- collect =<< forallS @EmployeeStatus+ success -- We can also make sure we generate distinct values for a given type test_distinct_values =- prop "we can generate distinct values for a given data type when used in a specific context" $ runS generators $ do- setDistinctForS @Department @Text- -- uncomment to check- -- collect =<< departmentName <$> forallS @Department- success-+ prop "we can generate distinct values for a given data type when used in a specific context" $+ runS generators $ do+ setDistinctForS @Department @Text+ -- uncomment to check+ -- collect =<< departmentName <$> forallS @Department+ success test_ints_generator = prop "we can generate ints" $ do- n <- forAllT distinctInt- n === n -- collect n+ n <- forAllT distinctInt+ n === n -- collect n test_fresh = minTestsOk 10000 $ prop "we can generate terms with fresh ids" $ do@@ -140,8 +173,8 @@ t <- g makeValue t -data Term =- Value Int Text+data Term+ = Value Int Text | Exp Int Term Term deriving (Eq, Show) @@ -162,7 +195,7 @@ runFresh = forAll . runStateGen runStateGen :: (Show a) => GenT (State Int) a -> Gen a-runStateGen = hoist (pure . flip evalState 0 )+runStateGen = hoist (pure . flip evalState 0) instance Fresh (GenT (State Int)) where fresh = do@@ -173,20 +206,18 @@ -- | Sample GenT IO values sampleGenIO :: GenT IO a -> IO a sampleGenIO gen =- let- loop n =- if n <= 0 then- panic "Hedgehog.Gen.sample: too many discards, could not generate a sample"- else do- seed <- Seed.random- NodeT r _ <- runTreeT $ evalGenT 30 seed gen- case r of- Nothing ->- loop (n - 1)- Just a ->- pure a- in- loop (100 :: Int)+ let loop n =+ if n <= 0+ then panic "Hedgehog.Gen.sample: too many discards, could not generate a sample"+ else do+ seed <- Seed.random+ NodeT r _ <- runTreeT $ evalGenT 30 seed gen+ case r of+ Nothing ->+ loop (n - 1)+ Just a ->+ pure a+ in loop (100 :: Int) {-# NOINLINE distinctInt #-} distinctInt :: GenIO Int
test/Test/Tutorial/DataModel.hs view
@@ -1,25 +1,28 @@ module Test.Tutorial.DataModel where -import Protolude+import Protolude -data Company = Company {- companyName :: Text-, departments :: [Department]-} deriving (Eq, Show)+data Company = Company+ { companyName :: Text,+ departments :: [Department]+ }+ deriving (Eq, Show) -data Department = Department {- departmentName :: Text-, employees :: [Employee]-} deriving (Eq, Show)+data Department = Department+ { departmentName :: Text,+ employees :: [Employee]+ }+ deriving (Eq, Show) -data Employee = Employee {- employeeName :: Text-, employeeStatus :: EmployeeStatus-, salary :: Int-, bonus :: Maybe Int-} deriving (Eq, Show)+data Employee = Employee+ { employeeName :: Text,+ employeeStatus :: EmployeeStatus,+ salary :: Int,+ bonus :: Maybe Int+ }+ deriving (Eq, Show) -data EmployeeStatus =- Permanent- | Temporary Int -- number of days- deriving (Eq, Show)+data EmployeeStatus+ = Permanent+ | Temporary Int -- number of days+ deriving (Eq, Show)
test/Test/Tutorial/Exercise1.hs view
@@ -1,26 +1,26 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE PartialTypeSignatures #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} module Test.Tutorial.Exercise1 where -import Data.Registry-import Data.Registry.Hedgehog-import Hedgehog hiding (test)-import Hedgehog.Gen-import Hedgehog.Range-import Protolude-import Test.Tutorial.DataModel+import Data.Registry+import Data.Registry.Hedgehog+import Hedgehog hiding (test)+import Hedgehog.Gen+import Hedgehog.Range+import Protolude+import Test.Tutorial.DataModel registry :: Registry _ _ registry =- genFun Company- <: genFun Department- <: genFun Employee- <: genVal genEmployeeStatus- <: genVal genInt- <: genVal genText+ genFun Company+ <: genFun Department+ <: genFun Employee+ <: genVal genEmployeeStatus+ <: genVal genInt+ <: genVal genText genInt :: Gen Int genInt = integral (linear 1 3)
test/Test/Tutorial/Exercise2.hs view
@@ -1,30 +1,30 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE PartialTypeSignatures #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} module Test.Tutorial.Exercise2 where -import Data.Registry-import Data.Registry.Hedgehog-import Hedgehog hiding (test)-import Hedgehog.Gen-import Hedgehog.Range-import Protolude-import Test.Tasty.Hedgehogx-import Test.Tutorial.DataModel+import Data.Registry+import Data.Registry.Hedgehog+import Hedgehog hiding (test, (===))+import Hedgehog.Gen+import Hedgehog.Range+import Protolude+import Test.Tasty.Hedgehogx+import Test.Tutorial.DataModel registry :: Registry _ _ registry =- genFun Company- <: genFun Department- <: genFun Employee- <: fun (listOf @Employee)- <: fun (listOf @Department)- <: fun (maybeOf @Int)- <: genVal genEmployeeStatus- <: genVal genInt- <: genVal genText+ genFun Company+ <: genFun Department+ <: genFun Employee+ <: fun (listOf @Employee)+ <: fun (listOf @Department)+ <: fun (maybeOf @Int)+ <: genVal genEmployeeStatus+ <: genVal genInt+ <: genVal genText genInt :: Gen Int genInt = integral (linear 1 3)@@ -39,8 +39,8 @@ makeCompanyGen :: GenIO Company makeCompanyGen = make @(GenIO Company) registry -forall :: forall a . (Typeable a, Show a) => PropertyT IO a-forall = forAllT $ genWith @a registry+forall :: forall a. (Typeable a, Show a) => PropertyT IO a+forall = withFrozenCallStack $ forAllT $ genWith @a registry test_company = test "make a company" $ do _ <- forall @Company
test/Test/Tutorial/Exercise3.hs view
@@ -1,25 +1,25 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE PartialTypeSignatures #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} module Test.Tutorial.Exercise3 where -import Data.Registry-import Data.Registry.Hedgehog-import Data.Registry.Hedgehog.TH-import Hedgehog hiding (test)-import Protolude-import Test.Tasty.Hedgehogx-import Test.Tutorial.DataModel-import Test.Tutorial.Exercise2 (registry)+import Data.Registry+import Data.Registry.Hedgehog+import Data.Registry.Hedgehog.TH+import Hedgehog hiding (test)+import Protolude+import Test.Tasty.Hedgehogx+import Test.Tutorial.DataModel+import Test.Tutorial.Exercise2 (registry) registry3 :: Registry _ _ registry3 = $(makeGenerators ''EmployeeStatus) <: registry -forall :: forall a . (Typeable a, Show a) => PropertyT IO a-forall = forAllT $ genWith @a registry3+forall :: forall a. (Typeable a, Show a) => PropertyT IO a+forall = withFrozenCallStack $ forAllT $ genWith @a registry3 test_employee_status = prop "make an employee status" $ do status <- forall @EmployeeStatus
test/Test/Tutorial/Exercise4.hs view
@@ -1,19 +1,19 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE PartialTypeSignatures #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} module Test.Tutorial.Exercise4 where -import Data.Registry-import Data.Registry.Hedgehog-import Data.Text as T-import Hedgehog hiding (test)-import Protolude-import Test.Tasty.Hedgehogx-import Test.Tutorial.DataModel-import Test.Tutorial.Exercise2 (genText)-import Test.Tutorial.Exercise3 (registry3)+import Data.Registry+import Data.Registry.Hedgehog+import Data.Text as T+import Hedgehog hiding (test)+import Protolude+import Test.Tasty.Hedgehogx+import Test.Tutorial.DataModel+import Test.Tutorial.Exercise2 (genText)+import Test.Tutorial.Exercise3 (registry3) registry12 :: Registry _ _ registry12 = specializeGen @Department genDepartmentName $ registry3@@ -21,8 +21,8 @@ genDepartmentName :: Gen Text genDepartmentName = T.take 5 . T.toUpper <$> genText -forall :: forall a . (Typeable a, Show a) => PropertyT IO a-forall = forAllT $ genWith @a registry12+forall :: forall a. (Typeable a, Show a) => PropertyT IO a+forall = withFrozenCallStack $ forAllT $ genWith @a registry12 test_deparment_name = prop "make a department" $ do department <- forall @Department
test/Test/Tutorial/Exercise5.hs view
@@ -1,20 +1,20 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE PartialTypeSignatures #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} module Test.Tutorial.Exercise5 where -import Data.Registry-import Data.Text as T-import Data.Registry.Hedgehog-import Hedgehog hiding (test)-import Protolude-import Test.Tasty.Hedgehogx-import Test.Tutorial.DataModel-import Test.Tutorial.Exercise2 (genText)-import Test.Tutorial.Exercise3 (registry3)-import Test.Tutorial.Exercise4 (genDepartmentName)+import Data.Registry+import Data.Registry.Hedgehog+import Data.Text as T+import Hedgehog hiding (test)+import Protolude+import Test.Tasty.Hedgehogx+import Test.Tutorial.DataModel+import Test.Tutorial.Exercise2 (genText)+import Test.Tutorial.Exercise3 (registry3)+import Test.Tutorial.Exercise4 (genDepartmentName) runGens = runS registry3 @@ -22,14 +22,18 @@ genEmployeeName = T.take 10 . T.toLower <$> genText setDepartmentName = specializeGenS @Department genDepartmentName-setEmployeeName = specializeGenS @Employee genEmployeeName +setEmployeeName = specializeGenS @Employee genEmployeeName+ setOneDepartment = addFunS $ listOfMinMax @Department 1 1-setOneEmployee = addFunS $ listOfMinMax @Employee 1 1++setOneEmployee = addFunS $ listOfMinMax @Employee 1 1+ setSmallCompany = setOneEmployee >> setOneDepartment -test_small_company = prop "make a small company" $ runGens $ do- setSmallCompany- setEmployeeName- setDepartmentName- collect =<< forallS @Company+test_small_company = prop "make a small company" $+ runGens $ do+ setSmallCompany+ setEmployeeName+ setDepartmentName+ collect =<< forallS @Company
test/Test/Tutorial/Exercise6.hs view
@@ -1,27 +1,28 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE PartialTypeSignatures #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures #-} module Test.Tutorial.Exercise6 where -import Data.Registry.Hedgehog-import Data.Text as T-import Hedgehog hiding (test)-import Protolude-import Test.Tasty.Hedgehogx-import Test.Tutorial.DataModel-import Test.Tutorial.Exercise5+import Data.Registry.Hedgehog+import Data.Text as T+import Hedgehog hiding (test)+import Protolude+import Test.Tasty.Hedgehogx+import Test.Tutorial.DataModel+import Test.Tutorial.Exercise5 -test_another_small_company = prop "make a small company" $ runGens $ do- setSmallCompany- setEmployeeName- setDepartmentName- setGenS @Int (pure 1)- setDistinctForS @Department @Text+test_another_small_company = prop "make a small company" $+ runGens $ do+ setSmallCompany+ setEmployeeName+ setDepartmentName+ setGenS @Int (pure 1)+ setDistinctForS @Department @Text - collect =<< forallS @Company+ collect =<< forallS @Company - setCycleChooserS @EmployeeStatus- collect =<< forallS @EmployeeStatus+ setCycleChooserS @EmployeeStatus+ collect =<< forallS @EmployeeStatus
test/test.hs view
@@ -1,5 +1,5 @@-import AutoDiscoveredSpecs (tests)-import Protolude-import Test.Tasty.Hedgehogx+import AutoDiscoveredSpecs (tests)+import Protolude+import Test.Tasty.Hedgehogx main = tests >>= defaultMain . groupByModuleName