ruin-0.1.0.0: test/StrictCheck.hs
{-# Language LambdaCase #-}
{-# Language TypeOperators #-}
{-# Language ViewPatterns #-}
-- | This is a small wrapper around the generation capability of the
-- @smallcheck@ library.
--
-- The key idea is that everywhere smallcheck would create a
-- non-strict data constructor, it should also include the possibility
-- that that position is an exception. In any given generated value,
-- each such exception is unique. This means that if two functions
-- applies to that same generated value always produce either the same
-- value or the same exception, then those functions have the exact
-- same strictness.
module StrictCheck (
SSerial(..),
sameStrictness,
sdecDepth,
(<~>),
) where
import Control.Applicative ((<|>))
import Control.Exception
import Control.Monad (guard)
import Control.Monad.State (State,evalState,get,modify)
import Control.Monad.Trans (lift)
import Test.SmallCheck.Series
import Data.Ruin ((:@),dub)
import Data.Ruin.Ancillaries (Pair(..),Tup1(..),mkLabel)
data Landmine = MkLandmine {unLandmine :: Int} deriving (Eq,Show)
instance Exception Landmine
type SSeries = Series (State Int)
-- | Use instead of 'decDepth'.
sdecDepth :: SSeries a -> SSeries a
sdecDepth m = do
do d <- getDepth
guard $ d > 0
localDepth (subtract 1) $ do
n <- lift get
lift $ modify (+1)
pure (throw (MkLandmine n)) <|> m
-- | Use instead of 'Serial'.
class SSerial a where sseries :: SSeries a
instance SSerial () where sseries = sdecDepth $ pure ()
instance SSerial a => SSerial (Tup1 a) where sseries = sdecDepth $ MkTup1 <$> sseries
instance (SSerial a,SSerial b) => SSerial (a,b) where
sseries = sdecDepth $ (,) <$> sseries <~> sseries
instance (SSerial a,SSerial b,SSerial c) => SSerial (a,b,c) where
sseries = sdecDepth $ (,,) <$> sseries <~> sseries <~> sseries
instance (SSerial a,SSerial b,SSerial c,SSerial d) => SSerial (a,b,c,d) where
sseries = sdecDepth $ (,,,) <$> sseries <~> sseries <~> sseries <~> sseries
instance SSerial a => SSerial (s :@ a) where
sseries = dub mkLabel <$> sseries -- newtypes don't have depth
instance (SSerial a,SSerial b) => SSerial (Pair a b) where
sseries = sdecDepth $ MkPair <$> sseries <~> sseries
andM :: Monad m => [m Bool] -> m Bool
andM = \case
[] -> return True
m:ms -> m >>= \case
True -> andM ms
False -> return False
-- | Returns 'True' iff @f@ and @g@ force the same parts of @a@ for all
-- possible inputs up to the given depth. Although, if @f@ or @g@
-- throws some exception other than the 'Landmine's this library
-- creates, this function returns 'False'.
sameStrictness :: SSerial a => (a -> b) -> (a -> c) -> Int -> IO Bool
sameStrictness f g depth = andM $ map test xs
where
xs = flip evalState 0 $ listM depth sseries
test x = do
fx <- try $ evaluate $ f x
gx <- try $ evaluate $ g x
case (fx,gx) of
(Right _,Right _) -> return True
(,)
(Left (fromException -> Just l))
(Left (fromException -> Just r)) -> return $ unLandmine l == unLandmine r
_ -> return False