smartcheck-0.2: src/Test/SmartCheck/Reduce.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Test.SmartCheck.Reduce
(smartRun
) where
import Test.SmartCheck.Args
import Test.SmartCheck.Types
import Test.SmartCheck.SmartGen
import Test.SmartCheck.DataToTree
import Test.SmartCheck.Render
import qualified Test.QuickCheck as Q
import Data.Typeable
import Data.Tree
import Data.Maybe
import Control.Monad (liftM)
--------------------------------------------------------------------------------
-- Smarter than shrinks. Does substitution. m is a value that failed QC that's
-- been shrunk. We substitute successive children with strictly smaller (and
-- increasingly larger) randomly-generated values until we find a failure, and
-- return that result. (We call smartShrink recursively.)
smartRun :: SubTypes a => ScArgs -> a -> (a -> Q.Property) -> IO a
smartRun args res prop = do
putStrLn ""
smartPrtLn "Smart Shrinking ..."
new <- smartShrink args res prop
smartPrtLn "Smart-shrunk value:"
print new
return new
--------------------------------------------------------------------------------
-- | Breadth-first traversal of d, trying to shrink it with *strictly* smaller
-- children. We replace d whenever a successful shrink is found and try again.
smartShrink :: forall a. SubTypes a => ScArgs -> a -> (a -> Q.Property) -> IO a
smartShrink args d prop =
liftM fst $ iter' d (mkForest d) (Idx 0 0)
where
mkForest x = mkSubstForest x True
notProp = Q.expectFailure . prop
iter' x forest_ idx' =
iter x test next notProp (scMaxDepth args) forest_ idx'
(errorMsg "next-idxs")
--------------------------------------
-- next tells the iter what to do after running a test.
next :: a -> Maybe a -> Forest Bool -> Idx -> [Idx] -> IO (a, [Idx])
next x res forest idx _ =
case res of
-- Found a try that fails prop. We'll now test try, and start trying to
-- reduce from the top!
Just y -> iter' y (mkForest y) (Idx 0 0)
-- Either couldn't satisfy the precondition or nothing satisfied the
-- property. Either way, we can't shrink it.
Nothing -> iter' x forest idx { column = column idx + 1 }
--------------------------------------
-- Our test function. First, we'll see if we can just return the hole at idx,
-- assuming it's (1) well-typed and (2), fails the test. Otherwise, we'll
-- test x by replacing values at idx against (Q.expectFailure . prop). Make
-- sure that values generated are strictly smaller than the value at
-- idx.
test :: a -> Idx -> IO (Maybe a)
test x idx = do
let vm = getAtIdx x idx (scMaxDepth args)
case vm of
Nothing -> errorMsg "smartShrink0"
Just v -> do
hole <- testHole v
if isJust hole then return hole
else do (_, r) <- iterateArb x v idx (scMaxReduce args)
-- Maximum size of values to generate; the minimum
-- of the value at the current index and the
-- maxSize parameter.
(min (subValSize x idx) (scMaxSize args))
notProp
return $ resultToMaybe r
where
testHole :: SubT -> IO (Maybe a)
testHole SubT { unSubT = v } =
maybe (return Nothing) extractAndTest (cast v :: Maybe a)
where
extractAndTest :: a -> IO (Maybe a)
extractAndTest y = do
res <- resultify notProp y
return $ resultToMaybe res
resultToMaybe :: Result a -> Maybe a
resultToMaybe res =
case res of
FailedPreCond -> Nothing
FailedProp -> Nothing
Result n -> Just n
--------------------------------------------------------------------------------
-- | Get the maximum depth of d's subforest at idx. Intuitively, it's the
-- maximum number of constructors you have *below* the constructor at idx. So
-- for a unary constructor C, the value [C, C, C]
--
-- (:) C
-- (:) C
-- (:) C []
--
-- At (Idx 0 0) in v, we're at C, so subValSize v (Idx 0 0) == 0.
-- At (Idx 0 1) in v, we're at (C : C : []), so subValSize v (Idx 0 1) == 2, since
-- we have the constructors :, C (or :, []) in the longest path underneath.
-- Base-types have subValSize 0. So subValSize [1,2,3] idx == 0 for any idx.
-- Note that if we have subValSize d idx == 0, then it is impossible to construct a
-- *structurally* smaller value at hole idx.
subValSize :: SubTypes a => a -> Idx -> Int
subValSize d idx = maybe 0 depth forestIdx
where
forestIdx :: Maybe [Tree Bool]
forestIdx = fmap subForest $ getIdxForest (mkSubstForest d True) idx
--------------------------------------------------------------------------------