{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleInstances #-} -- | Interface module. module Test.SmartCheck ( -- ** Main SmartCheck interface. smartCheck -- ** User-suppplied counterexample interface. , smartCheckInput -- ** Run QuickCheck and get a result. , runQC -- ** Arguments , module Test.SmartCheck.Args -- ** Main type class based on Generics. , SubTypes(..) -- ** For constructing new instances of `SubTypes` , gst , grc , gtc , gsf -- , gsz ) where import Test.SmartCheck.Args import Test.SmartCheck.ConstructorGen import Test.SmartCheck.Extrapolate import Test.SmartCheck.Matches import Test.SmartCheck.Reduce import Test.SmartCheck.Render import Test.SmartCheck.Test import Test.SmartCheck.Types import qualified Test.QuickCheck as Q import Generics.Deriving import Control.Monad (when) -------------------------------------------------------------------------------- -- | Main interface function. smartCheck :: ( SubTypes a , Generic a, ConNames (Rep a) , Q.Testable prop ) => ScArgs -> (a -> prop) -> IO () smartCheck args scProp = smartCheckRun args =<< runQC (qcArgs args) scProp smartCheckInput :: forall a prop. ( SubTypes a , Generic a, ConNames (Rep a) , Q.Testable prop , Read a ) => ScArgs -> (a -> prop) -> IO () smartCheckInput args scProp = do smartPrtLn "Input value to SmartCheck:" mcex <- fmap Just (readLn :: IO a) smartCheckRun args (mcex, Q.property . scProp) smartCheckRun :: forall a. ( SubTypes a , Generic a, ConNames (Rep a) ) => ScArgs -> (Maybe a, a -> Q.Property) -> IO () smartCheckRun args (origMcex, origProp) = do putStrLn "" smartPrtLn $ "Analyzing the first argument of the property with SmartCheck..." smartPrtLn $ "(If any stage takes too long, modify SmartCheck's arguments.)" smartCheck' [] origMcex origProp where smartCheck' :: [(a, Replace Idx)] -> Maybe a -> (a -> Q.Property) -> IO () smartCheck' ds mcex prop = maybe (maybeDoneMsg >> return ()) go mcex where go cex = do -- Run the smart reduction algorithm. d <- smartRun args cex prop -- If we asked to extrapolate values, do so. valIdxs <- forallExtrap args d origProp -- If we asked to extrapolate constructors, do so, again with the -- original property. csIdxs <- existsExtrap args d valIdxs origProp let replIdxs = Replace valIdxs csIdxs -- If either kind of extrapolation pass yielded fruit, prettyprint it. showExtrapOutput args valIdxs csIdxs replIdxs d -- Try again? runAgainMsg s <- getLine if s == "" -- If so, then loop, with the new prop. then do let oldVals = (d,replIdxs):ds let matchesProp a = not (matchesShapes a oldVals) Q.==> prop a (mcex', _) <- runQC (qcArgs args) (Q.noShrinking . matchesProp) smartCheck' oldVals mcex' matchesProp else smartPrtLn "Done." maybeDoneMsg = smartPrtLn "No value to smart-shrink; done." -------------------------------------------------------------------------------- existsExtrap :: (Generic a, SubTypes a, ConNames (Rep a)) => ScArgs -> a -> [Idx] -> (a -> Q.Property) -> IO [Idx] existsExtrap args d valIdxs origProp = if runExists args then constrsGen args d origProp valIdxs else return [] -------------------------------------------------------------------------------- forallExtrap :: SubTypes a => ScArgs -> a -> (a -> Q.Property) -> IO [Idx] forallExtrap args d origProp = if runForall args then -- Extrapolate with the original property to see if we -- get a previously-visited value back. extrapolate args d origProp else return [] -------------------------------------------------------------------------------- showExtrapOutput :: SubTypes a1 => ScArgs -> [a] -> [a] -> Replace Idx -> a1 -> IO () showExtrapOutput args valIdxs csIdxs replIdxs d = when (runForall args || runExists args) $ do if null (valIdxs ++ csIdxs) then smartPrtLn "Could not extrapolate a new value." else output where output = do putStrLn "" smartPrtLn "Extrapolated value:" renderWithVars (format args) d replIdxs -------------------------------------------------------------------------------- runAgainMsg :: IO () runAgainMsg = putStrLn $ "\nAttempt to find a new counterexample?\n" ++ " ('Enter' to continue;" ++ " any character then 'Enter' to quit.)" -------------------------------------------------------------------------------- -- | Run QuickCheck, to get a counterexamples for each argument, including the -- one we want to focus on for SmartCheck, which is the first argument. That -- argument is never shrunk by QuickCheck, but others may be shrunk by -- QuickCheck. Returns the value (if it exists) and a 'Property' (by applying -- the 'property' method to the 'Testable' value). In each iteration of -- 'runQC', non-SmartCheck arguments are not necessarily held constant runQC :: forall a prop . (Show a, Q.Arbitrary a, Q.Testable prop) => Q.Args -> (a -> prop) -> IO (Maybe a, a -> Q.Property) runQC args scProp = do smartPrtLn "Finding a counterexample with QuickCheck..." -- smartPrtLn " (mCex, res) <- scQuickCheckWithResult args scProp return $ if failureRes res then (mCex, Q.property . scProp) else (Nothing, Q.property . scProp) -- | Returns 'True' if a counterexample is returned and 'False' otherwise. failureRes :: Q.Result -> Bool failureRes res = case res of Q.Failure _ _ _ _ _ _ _ _ _ _ -> True _ -> False --------------------------------------------------------------------------------