packages feed

nat-sized-numbers-0.3.0.0: test/Spec.hs

{-# LANGUAGE TypeApplications    #-}
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

import           Hedgehog
import qualified Hedgehog.Gen       as Gen
import qualified Hedgehog.Range     as Range
import           Test.DocTest

import           Numeric.Sized.WordOfSize
import           Numeric.Sized.IntOfSize

import           Control.Monad

import           Data.Data

default ()

binaryProp
    :: forall a.
       Integral a
    => (forall t. Integral t => t -> t -> t)
    -> Integer
    -> Integer
    -> (Integer -> Integer -> Bool)
    -> Property
binaryProp op lb ub cond = property $ do
    x <- forAll (Gen.integral (Range.linear lb ub))
    y <- forAll (Gen.integral (Range.linear lb ub))
    guard (cond x y)
    let zb = op x y
    let zt = op (fromInteger x :: a) (fromInteger y)
    zb === toInteger zt

ordProps
    :: forall a.
       (Ord a, Show a, Typeable a)
    => Gen IO a
    -> Property
ordProps xs = property $ do
    x <- forAll xs
    info "reflexive"
    x === x
    info "irreflexive"
    assert (not (x < x))
    y <- forAll xs
    info "Ord functions behave same as default implementations"
    case compare x y of
      LT -> do
          assert (x < y)
          assert (x /= y)
          assert (not (x == y))
          assert (x <= y)
          assert (not (x >= y))

          info "antisymmetric"
          assert (y > x)
          info "irreflexive"
          assert (not (x > y))
          info "transitive"
          z <- forAll xs
          when (z > y) (assert (z > x))
      EQ -> do
          assert (x == y)
          assert (not (x /= y))
          assert (not (x < y))
          assert (not (x > y))
          assert (x <= y)
          assert (x >= y)

          info "symmetric"
          assert (y == x)
          info "transitive"
          z <- forAll xs
          assert $ (x == z) == (y == z)
      GT -> do
          assert (x > y)
          assert (x /= y)
          assert (not (x == y))
          assert (x >= y)
          assert (not (x <= y))

          info "irreflexive"
          assert (not (x < y))
          info "antisymmetric"
          assert (y < x)
          info "transitive"
          z <- forAll xs
          when (z < y) (assert (z < x))

holdsForLength :: Foldable f => (a -> Bool) -> f a -> Int
holdsForLength p = flip (foldr f id ) 0 where
  f e a i | p e = a (i + 1)
          | otherwise = i

enumProps
    :: forall a.
       (Enum a, Show a, Typeable a, Ord a)
    => (Int -> Bool) -> Gen IO Int -> Gen IO a -> Property
enumProps p ig eg = property $ do
    x <- forAll ig
    info "from . to"
    (fromEnum . toEnum @a) x === x
    info "to . from"
    n <- forAll eg
    (toEnum . fromEnum) n === n
    info "[n..]"
    let lhs1 = take 100 $ map fromEnum [n..]
        rhs1 = take 100 $ [fromEnum n..]
        len1 = min (holdsForLength p lhs1) (holdsForLength p rhs1)
    take len1 lhs1 === take len1 rhs1
    info "[n,m..]"
    m <- forAll eg
    let lhs2 = take 100 $ map fromEnum [n,m..]
        rhs2 = take 100 $ [fromEnum n, fromEnum m..]
        len2 = min (holdsForLength p lhs2) (holdsForLength p rhs2)
    take len2 lhs2 === take len2 rhs2
    when (m >= n) $ do
        info "[n..m]"
        map fromEnum [n..m] === [fromEnum n..fromEnum m]
    l <- forAll eg
    when (((l > n) == (n > m)) && (l /= n)) $ do
        info "[l,n..m]"
        map fromEnum [l,n..m] === [fromEnum l, fromEnum n..fromEnum m]


prop_Word3Add :: Property
prop_Word3Add = binaryProp @(WordOfSize 3) (+) 0 7 (\x y -> x + y <= 7)

prop_Word3Mul :: Property
prop_Word3Mul = binaryProp @(WordOfSize 3) (*) 0 7 (\x y -> x * y <= 7)

prop_Word3Sub :: Property
prop_Word3Sub = withDiscards 1000 $ binaryProp @(WordOfSize 3) (-) 0 7 (>=)

prop_Word3Rem :: Property
prop_Word3Rem = binaryProp @(WordOfSize 3) rem 0 7 (\_ y -> y > 0)

prop_Word3Quot :: Property
prop_Word3Quot = binaryProp @(WordOfSize 3) quot 0 7 (\_ y -> y > 0)

prop_Word3Ord :: Property
prop_Word3Ord = ordProps (Gen.integral (Range.linear @(WordOfSize 3) 0 7))

prop_Word3Enum :: Property
prop_Word3Enum =
    enumProps
        (inBounds 0 7)
        (Gen.integral (Range.linear 0 7))
        (Gen.integral (Range.linear @(WordOfSize 3) 0 7))

inBounds :: Ord a => a -> a -> a -> Bool
inBounds lb ub x = x >= lb && x <= ub

prop_Int3Add :: Property
prop_Int3Add =
    withDiscards 1000 $
    binaryProp
        @(IntOfSize 3)
        (+)
        (-4)
        3
        (\x y ->
              inBounds (-4) 3 (x + y))

prop_Int3Mul :: Property
prop_Int3Mul =
    withDiscards 1000 $
    binaryProp
        @(IntOfSize 3)
        (*)
        (-4)
        3
        (\x y ->
              inBounds (-4) 3 (x * y))

prop_Int3Sub :: Property
prop_Int3Sub = binaryProp @(IntOfSize 3) (-) (-4) 3 (\x y -> inBounds (-4) 3 (x - y))

prop_Int3Rem :: Property
prop_Int3Rem = binaryProp @(IntOfSize 3) rem (-4) 3 (\_ y -> y /= 0)

prop_Int3Quot :: Property
prop_Int3Quot = binaryProp @(IntOfSize 3) quot (-4) 3 (\x y -> y /= 0 && inBounds (-4) 3 (quot x y))

prop_Int3Ord :: Property
prop_Int3Ord = ordProps (Gen.integral (Range.linear @(IntOfSize 3) (-3) 4))

prop_Int3Enum :: Property
prop_Int3Enum =
    enumProps
        (inBounds (-4) 3)
        (Gen.integral (Range.linear (-4) 3))
        (Gen.integral (Range.linear @(IntOfSize 3) (-4) 3))

main :: IO Bool
main = do
    doctest ["-isrc","src/"]
    $$(checkConcurrent)