packages feed

grisette-0.7.0.0: test/Grisette/Core/Data/Class/SafeDivisionTests.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Grisette.Core.Data.Class.SafeDivisionTests (safeDivisionTests) where

import Control.DeepSeq (NFData, force)
import Control.Exception (ArithException, catch)
import Control.Monad.Except (ExceptT, runExceptT)
import Data.Bifunctor (Bifunctor (bimap))
import Data.Data (Typeable, typeRep)
import Data.Proxy (Proxy (Proxy))
import GHC.IO (evaluate)
import GHC.Int (Int16, Int32, Int64, Int8)
import GHC.Word (Word16, Word32, Word64, Word8)
import Grisette
  ( BV (bv),
    BitwidthMismatch (BitwidthMismatch),
    IntN,
    Mergeable,
    SafeDivision (safeDiv, safeDivMod, safeMod, safeQuot, safeQuotRem, safeRem),
    SomeIntN,
    SomeWordN,
    Union,
    WordN,
    mrgPure,
    pattern SomeIntN,
    pattern SomeWordN,
  )
import Grisette.Internal.Core.Control.Monad.Union (isMerged)
import Grisette.Lib.Control.Monad.Except (mrgThrowError)
import Test.Framework (Test, testGroup)
import Test.Framework.Providers.HUnit (testCase)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.HUnit (assertBool, (@?=))
import Test.QuickCheck (Arbitrary, ioProperty)

matches ::
  (NFData r, Eq r', Show r', Mergeable r', Mergeable e, Eq e, Show e) =>
  (t -> t') ->
  (r -> r') ->
  (ArithException -> e) ->
  (t' -> t' -> ExceptT e Union r') ->
  (t -> t -> r) ->
  t ->
  t ->
  IO ()
matches wrapInput wrapOutput wrapError f fref x y = do
  rref <-
    (mrgPure . wrapOutput <$> evaluate (force (fref x y)))
      `catch` \(e :: ArithException) -> return $ mrgThrowError $ wrapError e
  let r = f (wrapInput x) (wrapInput y)
  assertBool "Is merged" $ isMerged $ runExceptT r
  r @?= rref

generalOpTestBase ::
  ( NFData r,
    Arbitrary t,
    Show t,
    Eq r',
    Show r',
    Eq r,
    Num t,
    Mergeable r',
    Mergeable e,
    Show e,
    Eq e
  ) =>
  (t -> t') ->
  (r -> r') ->
  (ArithException -> e) ->
  String ->
  (t' -> t' -> ExceptT e Union r') ->
  (t -> t -> r) ->
  Test
generalOpTestBase wrapInput wrapOutput wrapError name f fref =
  testGroup
    name
    [ testProperty "random" $ \x y ->
        ioProperty $ matches wrapInput wrapOutput wrapError f fref x y,
      testCase "divided by zero" $
        matches wrapInput wrapOutput wrapError f fref 1 0
    ]

generalOpTest ::
  (NFData r, Arbitrary t, Show t, Eq r, Show r, Eq r, Num t, Mergeable r) =>
  String ->
  (t -> t -> ExceptT ArithException Union r) ->
  (t -> t -> r) ->
  Test
generalOpTest = generalOpTestBase id id id

opBoundedTestBase ::
  ( NFData r,
    Arbitrary t,
    Show t,
    Show r',
    Eq r',
    Num t,
    Bounded t,
    Mergeable r',
    Mergeable e,
    Show e,
    Eq e
  ) =>
  (t -> t') ->
  (r -> r') ->
  (ArithException -> e) ->
  String ->
  (t' -> t' -> ExceptT e Union r') ->
  (t -> t -> r) ->
  Test
opBoundedTestBase wrapInput wrapOutput wrapError name f fref =
  testGroup
    name
    [ testProperty "random" $ \x y ->
        ioProperty $ matches wrapInput wrapOutput wrapError f fref x y,
      testCase "divided by zero" $
        matches wrapInput wrapOutput wrapError f fref 1 0,
      testCase "minBound/-1" $
        matches wrapInput wrapOutput wrapError f fref minBound (-1)
    ]

opBoundedTest ::
  ( NFData r,
    Arbitrary t,
    Show t,
    Show r,
    Eq r,
    Num t,
    Bounded t,
    Mergeable r
  ) =>
  String ->
  (t -> t -> ExceptT ArithException Union r) ->
  (t -> t -> r) ->
  Test
opBoundedTest = opBoundedTestBase id id id

type OpTestFunc t =
  forall r.
  (Eq r, Show r, Eq r, NFData r, Mergeable r) =>
  String ->
  (t -> t -> ExceptT ArithException Union r) ->
  (t -> t -> r) ->
  Test

testType ::
  forall t.
  ( NFData t,
    Show t,
    SafeDivision ArithException t (ExceptT ArithException Union),
    Mergeable t,
    Integral t,
    Typeable t
  ) =>
  OpTestFunc t ->
  Proxy t ->
  Test
testType testFunc p =
  testGroup
    (show $ typeRep p)
    [ testFunc "div" safeDiv (div @t),
      testFunc "mod" safeMod (mod @t),
      testFunc "divMod" safeDivMod (divMod @t),
      testFunc "quot" safeQuot (quot @t),
      testFunc "rem" safeRem (rem @t),
      testFunc "quotRem" safeQuotRem (quotRem @t)
    ]

-- type SomeOpTestFunc t t' =
--   forall r r'.
--   (Eq r, Show r, Eq r, NFData r, Mergeable r) =>
--   String ->
--   (t' -> t' -> ExceptT ArithException Union r') ->
--   (t -> t -> r) ->
--   Test
--
-- testSomeType ::
--   forall t t'.
--   ( NFData t,
--     Show t,
--     SafeDivision ArithException t' (ExceptT ArithException Union),
--     Mergeable t,
--     Integral t,
--     Typeable t'
--   ) =>
--   SomeDivTestFunc t t' ->
--   SomeDivTestFunc t t' ->
--   Proxy t ->
--   Proxy t' ->
--   Test
-- testSomeType divQuotTest modRemTest _ p =
--   testGroup
--     (show $ typeRep p)
--     [ divQuotTest "div" safeDiv (div @t),
--       modRemTest "mod" safeMod (mod @t),
--       divQuotTest "divMod" safeDivMod (divMod @t),
--       divQuotTest "quot" safeQuot (quot @t),
--       modRemTest "rem" safeRem (rem @t),
--       modRemTest "quotRem" safeQuotRem (quotRem @t)
--     ]

safeDivisionTests :: Test
safeDivisionTests =
  testGroup
    "SafeDivision"
    [ testType generalOpTest (Proxy :: Proxy Integer),
      testType opBoundedTest (Proxy :: Proxy Int8),
      testType opBoundedTest (Proxy :: Proxy Int16),
      testType opBoundedTest (Proxy :: Proxy Int32),
      testType opBoundedTest (Proxy :: Proxy Int64),
      testType opBoundedTest (Proxy :: Proxy Int),
      testType opBoundedTest (Proxy :: Proxy (IntN 8)),
      testType opBoundedTest (Proxy :: Proxy Word),
      testType opBoundedTest (Proxy :: Proxy Word8),
      testType opBoundedTest (Proxy :: Proxy Word16),
      testType opBoundedTest (Proxy :: Proxy Word32),
      testType opBoundedTest (Proxy :: Proxy Word64),
      testType opBoundedTest (Proxy :: Proxy (WordN 8)),
      testGroup "SomeWordN" $ do
        let singleOutputTest =
              opBoundedTestBase
                SomeWordN
                SomeWordN
                (\e -> Right e :: Either BitwidthMismatch ArithException)
        let doubleOutputTest =
              opBoundedTestBase
                SomeWordN
                (bimap SomeWordN SomeWordN)
                (\e -> Right e :: Either BitwidthMismatch ArithException)
        [ singleOutputTest "div" safeDiv (div @(WordN 8)),
          singleOutputTest "mod" safeMod (mod @(WordN 8)),
          doubleOutputTest "divMod" safeDivMod (divMod @(WordN 8)),
          singleOutputTest "quot" safeQuot (quot @(WordN 8)),
          singleOutputTest "rem" safeRem (rem @(WordN 8)),
          doubleOutputTest "quotRem" safeQuotRem (quotRem @(WordN 8)),
          testCase "Bitwidth mismatch" $ do
            let actual =
                  safeDiv (bv 10 2) (bv 11 3) ::
                    ExceptT
                      (Either BitwidthMismatch ArithException)
                      Union
                      SomeWordN
            let expected = mrgThrowError $ Left BitwidthMismatch
            actual @?= expected
          ],
      testGroup "SomeIntN" $ do
        let singleOutputTest =
              opBoundedTestBase
                SomeIntN
                SomeIntN
                (\e -> Right e :: Either BitwidthMismatch ArithException)
        let doubleOutputTest =
              opBoundedTestBase
                SomeIntN
                (bimap SomeIntN SomeIntN)
                (\e -> Right e :: Either BitwidthMismatch ArithException)
        [ singleOutputTest "div" safeDiv (div @(IntN 8)),
          singleOutputTest "mod" safeMod (mod @(IntN 8)),
          doubleOutputTest "divMod" safeDivMod (divMod @(IntN 8)),
          singleOutputTest "quot" safeQuot (quot @(IntN 8)),
          singleOutputTest "rem" safeRem (rem @(IntN 8)),
          doubleOutputTest "quotRem" safeQuotRem (quotRem @(IntN 8)),
          testCase "Bitwidth mismatch" $ do
            let actual =
                  safeDiv (bv 10 2) (bv 11 3) ::
                    ExceptT
                      (Either BitwidthMismatch ArithException)
                      Union
                      SomeIntN
            let expected = mrgThrowError $ Left BitwidthMismatch
            actual @?= expected
          ]
    ]