-- | This module allows to use SmallCheck properties in tasty.
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts,
TypeOperators, DeriveDataTypeable, TypeFamilies,
GeneralizedNewtypeDeriving #-}
module Test.Tasty.SmallCheck
( testProperty
, SmallCheckDepth(..)
, module Test.SmallCheck
) where
import Test.Tasty.Providers
import Test.Tasty.Options
import qualified Test.SmallCheck as SC
import Test.SmallCheck hiding (smallCheck) -- for re-export
import Test.SmallCheck.Drivers as SC
import Control.Exception
import Control.Monad (when)
import Data.Typeable
import Data.IORef
import Options.Applicative (metavar)
import Text.Printf
-- | Create a 'Test' for a SmallCheck 'SC.Testable' property
testProperty :: SC.Testable IO a => TestName -> a -> TestTree
testProperty name prop = singleTest name $ (SC.test prop :: SC.Property IO)
-- | The \"depth\" parameter for SmallCheck
newtype SmallCheckDepth = SmallCheckDepth Int
deriving (Num, Ord, Eq, Real, Enum, Integral, Typeable)
instance IsOption SmallCheckDepth where
defaultValue = 5
parseValue = fmap SmallCheckDepth . safeRead
optionName = return "smallcheck-depth"
optionHelp = return "Depth to use for smallcheck tests"
optionCLParser = mkOptionCLParser $ metavar "NUMBER"
-- | The maximum number of test cases to generate. Can be used as an
-- alternative to setting 'SmallCheckDepth'.
newtype SmallCheckMaxCount = SmallCheckMaxCount Int
deriving (Num, Ord, Eq, Real, Enum, Integral, Typeable)
instance IsOption SmallCheckMaxCount where
defaultValue = SmallCheckMaxCount maxBound -- disable by default
parseValue = fmap SmallCheckMaxCount . safeRead
optionName = return "smallcheck-max-count"
optionHelp = return "Maximum smallcheck test count"
optionCLParser = mkOptionCLParser $ metavar "NUMBER"
instance IsTest (SC.Property IO) where
testOptions = return
[ Option (Proxy :: Proxy SmallCheckDepth)
, Option (Proxy :: Proxy SmallCheckMaxCount)
]
run opts prop yieldProgress = do
let
SmallCheckDepth depth = lookupOption opts
SmallCheckMaxCount maxCount = lookupOption opts
counter <- newIORef (0 :: Int, 0 :: Int)
let
hook quality = do
let
inc (total, bad) =
case quality of
GoodTest -> ((,) $! total + 1) bad
BadTest -> ((,) $! total + 1) $! bad + 1
count <- myAtomicModifyIORef' counter (\c -> let c' = inc c in (c', fst c'))
when (count >= maxCount) $ throwIO Finish
-- submit progress data to tasty
yieldProgress $ Progress
{ progressText = show count
, progressPercent = 0 -- we don't know the total number of tests
}
-- small check does not catch exceptions on its own, so lets do it
scResult <- try $ smallCheckWithHook depth hook prop
(total, bad) <- readIORef counter
let
desc
| bad == 0
= printf "%d tests completed" total
| otherwise
= printf "%d tests completed (but %d did not meet the condition)" total bad
return $
case scResult of
Left e
| Just Finish <- fromException e
-> testPassed desc
| otherwise -> testFailed $ show e
Right Nothing -> testPassed desc
Right (Just f) -> testFailed $ ppFailure f
data Finish = Finish
deriving (Eq, Show)
instance Exception Finish
-- Copied from base to stay compatible with GHC 7.4.
myAtomicModifyIORef' :: IORef a -> (a -> (a,b)) -> IO b
myAtomicModifyIORef' ref f = do
b <- atomicModifyIORef ref
(\x -> let (a, b) = f x
in (a, a `seq` b))
b `seq` return b