massiv-test-0.1.5: src/Test/Massiv/Utils.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Test.Massiv.Utils
( showsType
, showsArrayType
, assertException
, assertExceptionIO
, assertSomeException
, assertSomeExceptionIO
, toStringException
, ExpectedException(..)
, applyFun2Compat
, expectProp
-- * Epsilon comparison
, epsilonExpect
, epsilonFoldableExpect
, epsilonMaybeEq
, epsilonEq
, epsilonEqDouble
, epsilonEqFloat
, module X
) where
import qualified Data.Foldable as F
import Control.Monad as X
import Control.Monad.ST as X
import Data.Maybe as X (fromMaybe, isJust, isNothing)
import Data.Typeable as X
import Test.QuickCheck as X hiding ((.&.))
import Test.QuickCheck.Monadic as X
import Test.Hspec as X
import Test.Hspec.QuickCheck as X
import Test.QuickCheck.Function as X
import Control.DeepSeq as X (NFData, deepseq)
import UnliftIO.Exception (Exception(..), SomeException, catch, catchAny)
#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup as X ((<>))
#endif
-- | Use Typeable to show the type.
showsType :: forall t . Typeable t => ShowS
showsType = showsTypeRep (typeRep (Proxy :: Proxy t))
-- | Use Typeable to show the array type
showsArrayType :: forall r ix e . (Typeable r, Typeable ix, Typeable e) => ShowS
showsArrayType =
("Array " ++) . showsType @r . (" (" ++) . showsType @ix . (") " ++) . showsType @e
assertException ::
(Testable b, NFData a, Exception exc)
=> (exc -> b) -- ^ Return True if that is the exception that was expected
-> a -- ^ Value that should throw an exception, when fully evaluated
-> Property
assertException isExc = assertExceptionIO isExc . pure
assertSomeException :: NFData a => a -> Property
assertSomeException = assertSomeExceptionIO . pure
assertExceptionIO ::
(Testable b, NFData a, Exception exc)
=> (exc -> b) -- ^ Return True if that is the exception that was expected
-> IO a -- ^ IO Action that should throw an exception
-> Property
assertExceptionIO isExc action =
monadicIO $
run $
catch
(do res <- action
res `deepseq` return (counterexample "Did not receive an exception" False))
(\exc -> displayException exc `deepseq` return (property (isExc exc)))
assertSomeExceptionIO :: NFData a => IO a -> Property
assertSomeExceptionIO action =
monadicIO $
run $
catchAny
(do res <- action
res `deepseq` return (counterexample "Did not receive an exception" False))
(\exc -> displayException exc `deepseq` return (property True))
toStringException :: Either SomeException a -> Either String a
toStringException = either (Left . displayException) Right
data ExpectedException = ExpectedException deriving (Show, Eq)
instance Exception ExpectedException
applyFun2Compat :: Fun (a, b) c -> (a -> b -> c)
#if MIN_VERSION_QuickCheck(2,10,0)
applyFun2Compat = applyFun2
#else
applyFun2Compat (Fun _ f) a b = f (a, b)
instance Function Word where
function = functionMap fromIntegral fromInteger
#endif
-- | Convert an hspec Expectation to a quickcheck Property.
--
-- @since 1.5.0
expectProp :: Expectation -> Property
expectProp = monadicIO . run
epsilonExpect ::
(HasCallStack, Show a, RealFloat a)
=> a -- ^ Epsilon, a maximum tolerated error. Sign is ignored.
-> a -- ^ Expected result.
-> a -- ^ Tested value.
-> Expectation
epsilonExpect epsilon x y =
X.forM_ (epsilonMaybeEq epsilon x y) $ \errMsg ->
expectationFailure $ "Expected: " ++ show x ++ " but got: " ++ show y ++ "\n " ++ errMsg
epsilonFoldableExpect ::
(HasCallStack, Foldable f, Show (f e), Show e, RealFloat e) => e -> f e -> f e -> Expectation
epsilonFoldableExpect epsilon x y = do
F.length x `shouldBe` F.length y
unless (F.null x) $
X.forM_ (zipWithM (epsilonMaybeEq epsilon) (F.toList x) (F.toList y)) $ \errMsgs ->
expectationFailure $
"Expected: " ++ show x ++ " but got: " ++ show y ++ "\n" ++ unlines (fmap (" " ++) errMsgs)
epsilonMaybeEq ::
(Show a, RealFloat a)
=> a -- ^ Epsilon, a maximum tolerated error. Sign is ignored.
-> a -- ^ Expected result.
-> a -- ^ Tested value.
-> Maybe String
epsilonMaybeEq epsilon x y
| isNaN x && not (isNaN y) = Just $ "Expected NaN, but got: " ++ show y
| x == y = Nothing
| diff > n = Just $ concat [show x, " /= ", show y, " (Tolerance: ", show diff, " > ", show n, ")"]
| otherwise = Nothing
where
(absx, absy) = (abs x, abs y)
n = epsilon * (1 + max absx absy)
diff = abs (y - x)
epsilonEq ::
(Show a, RealFloat a)
=> a -- ^ Epsilon, a maximum tolerated error. Sign is ignored.
-> a -- ^ Expected result.
-> a -- ^ Tested value.
-> Property
epsilonEq epsilon x y = property $ epsilonExpect epsilon x y
epsilonEqDouble ::
Double -- ^ Expected result.
-> Double -- ^ Tested value.
-> Property
epsilonEqDouble = epsilonEq epsilon
where
epsilon = 1e-12
epsilonEqFloat ::
Float -- ^ Expected result.
-> Float -- ^ Tested value.
-> Property
epsilonEqFloat = epsilonEq epsilon
where
epsilon = 1e-6