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