packages feed

registry-hedgehog-0.2.1.1: test/Test/Data/Registry/HedgehogSpec.hs

{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE PartialTypeSignatures      #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
{-# 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 qualified Data.Text as T
import qualified Data.Text.IO as T
-- * 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

-- * DECLARING PROPERTIES

genWord :: MonadIO m => GenT m Text
genWord = do
  ws <- T.lines <$> liftIO (T.readFile "/usr/share/dict/words")
  Gen.element ws

test_a_simple_test =
  test "make a simple assertion, tested only once" $
    1 === 1

test_a_simple_property =
  prop "make a simple property, tested 100 times by default" $ do
    n <- forAll (integral (linear 1 3))
    n === n

test_a_property_200_times = minTestsOk 200 $
  prop "make a simple property, tested 200 times" $ do
    n <- forAll (integral (linear 1 3))
    n === n

test_a_property_no_shrinking = noShrink $
  prop "make a simple property, not shrinked in case of a failure" $ do
    n <- forAll (integral (linear 1 3))
    n === n

test_a_property_with_a_seed = withSeed "(Size 51) (Seed 35539461314630919 5029106023111197789)" $
  prop "make a simple property, with a specific seed to reproduce a failure" $ do
    n <- forAll (integral (linear 1 3))
    (n >= 1) === True

-- * USING GENERATORS

test_company_1 =
  prop "a company can be used for testing" $ do
    -- note that we are using forall and not forAll
    company <- forall @Company
    (length (departments company) >= 0) === True

-- Let's create some registry modifiers to constrain the generation
setOneDepartment = addFunS $ listOfMinMax @Department 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

-- * 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

    -- 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

-- 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


test_ints_generator =
  prop "we can generate ints" $ do
   n <- forAllT distinctInt
   n === n -- collect n

test_fresh = minTestsOk 10000 $
  prop "we can generate terms with fresh ids" $ do
    -- let termGenerator = genTerm :: GenT (StateT Int Identity) Term
    m <- forAll $ runStateGen genTerm
    -- collect m
    m === m

genFixedText :: (MonadGen m) => m Text
genFixedText = pure "xxx"

genTerm :: (MonadGen m, Fresh m) => m Term
genTerm = Gen.recursive Gen.choice [genValue genFixedText] [Gen.subtermM2 genTerm genTerm makeExp]

genValue :: (MonadGen m, Fresh m) => m Text -> m Term
genValue g = do
  t <- g
  makeValue t

data Term =
    Value Int Text
  | Exp Int Term Term
  deriving (Eq, Show)

makeValue :: (Monad m, Fresh m) => Text -> m Term
makeValue t = do
  n <- fresh
  pure (Value n t)

makeExp :: (Monad m, Fresh m) => Term -> Term -> m Term
makeExp t1 t2 = do
  n <- fresh
  pure (Exp n t1 t2)

class Fresh m where
  fresh :: m Int

runFresh :: (Show a) => GenT (State Int) a -> PropertyT IO a
runFresh = forAll . runStateGen

runStateGen :: (Show a) => GenT (State Int) a -> Gen a
runStateGen = hoist (pure . flip evalState 0  )

instance Fresh (GenT (State Int)) where
  fresh = do
    n <- get
    put (n + 1)
    pure n

-- | 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)

{-# NOINLINE distinctInt #-}
distinctInt :: GenIO Int
distinctInt = unsafePerformIO $ do
  ref <- newIORef (0 :: Int)
  pure $ distinctIntGenerator ref

distinctIntGenerator :: IORef Int -> GenIO Int
distinctIntGenerator ref = do
  i <- lift $ readIORef ref
  lift $ writeIORef ref (i + 1)
  pure i