lol-0.7.0.0: Crypto/Lol/Utils/Tests.hs
{-|
Module : Crypto.Lol.Utils.Tests
Description : Infrastructure for testing Lol.
Copyright : (c) Eric Crockett, 2011-2017
Chris Peikert, 2011-2017
License : GPL-3
Maintainer : ecrockett0@email.com
Stability : experimental
Portability : POSIX
Infrastructure for testing Lol.
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Crypto.Lol.Utils.Tests
(nestGroup
,testIOWithGen
,testIOWithoutGen
,testWithGen
,testWithoutGen
,ApproxEqual(..)
-- Re-exports
,QCG.choose
,QCG.chooseAny
,TF.testGroup
,QC.Gen
,TF.Test) where
import Crypto.Lol.Prelude (Complex, Int64, imag, real)
import Crypto.Lol.Types.Unsafe.ZqBasic
import qualified Test.Framework as TF
import qualified Test.Framework.Providers.QuickCheck2 as QC2
import qualified Test.QuickCheck as QC
import qualified Test.QuickCheck.Gen as QCG
import qualified Test.QuickCheck.Monadic as QCM
-- The allowable distance from 1 the ratio of two Doubles is allowed to be in order for the numbers
-- to be considered "approximately equal". That is, x =~= y iff -1-ε < x/y < 1+ε
-- In the case that x == 0, we just measure that abs y < ε, and vice-versa for y == 0
approxEqualEpsilon :: Double
approxEqualEpsilon = 1e-6
class ApproxEqual a where
(=~=) :: a -> a -> Bool
instance ApproxEqual Int64 where
x =~= y = x == y
instance Eq r => ApproxEqual (ZqBasic q r) where
x =~= y = x == y
instance ApproxEqual Double where
x =~= y
| x == 0 = abs y < approxEqualEpsilon
| y == 0 = abs x < approxEqualEpsilon
| isNaN x || isNaN y = False
-- Calculate the distance of x/y and y/x from 1 and take the minimum
| otherwise = let minDist = minimum $ map (\r -> abs $ abs r - 1) [x/y, y/x] in
minDist < approxEqualEpsilon
instance ApproxEqual (Complex Double) where
x =~= y = (real x =~= real y) && (imag x =~= imag y)
-- | Make a 'Test' given a name, a testing function, and a parameter generator
testWithGen :: (Show a, QC.Testable prop) => String -> (a -> prop) -> QC.Gen a -> TF.Test
testWithGen name f gen = QC2.testProperty name $ QC.forAll gen f
-- | Make a 'Test' given a name, a monadic (IO only) testing function, and a parameter generator
testIOWithGen :: (Show a, QC.Testable prop) => String -> (a -> IO prop) -> QC.Gen a -> TF.Test
testIOWithGen name f gen = QC2.testProperty name $ QCM.monadicIO $ QCM.forAllM gen (QCM.run . f)
-- | Make a 'Test' given a name and a 'QC.Testable' value
testWithoutGen :: (QC.Testable prop) => String -> prop -> TF.Test
testWithoutGen name p = QC2.testProperty name $ QC.property p
-- | Make a 'Test' given a name and a monadic (IO only) 'QC.Testable' value
testIOWithoutGen :: (QC.Testable prop) => String -> IO prop -> TF.Test
testIOWithoutGen name p = QC2.testProperty name $ QCM.monadicIO $ QCM.run p
-- | Apply parameters to a list of 'Test'.
nestGroup :: String -> [QC.Gen a -> TF.Test] -> QC.Gen a -> TF.Test
nestGroup name ts gen = TF.testGroup name $ map ($ gen) ts