packages feed

integer-types-0.1.4.0: test/Spec/Subtraction.hs

module Spec.Subtraction where

import Control.Exception (ArithException (Underflow))
import Control.Monad.Catch (MonadCatch, try)
import Data.Either qualified as Either
import Essentials
import Integer
  ( Integer,
    IntegerConvert (..),
    IntegerNarrow (..),
    Natural,
    Positive,
    Signed,
    Subtraction,
    Subtraction' (..),
  )
import Integer.Gen (GenIntegral)
import Integer.Gen qualified as Gen
import Test.Hspec (Spec, context, it)
import Test.Hspec.Hedgehog
  ( PropertyT,
    forAll,
    hedgehog,
    modifyMaxSuccess,
    (===),
  )
import Prelude (Num, toInteger, ($!), (-))

spec :: Spec
spec = do
  context "subtract in A behaves the same as (-) in B" do
    modifyMaxSuccess (\_ -> 1000) do
      it "A = Natural,  B = Signed" $ hedgehog $ checkSubtract @Natural @Signed
      it "A = Natural,  B = Integer" $ hedgehog $ checkSubtract @Natural @Integer
      it "A = Positive, B = Signed" $ hedgehog $ checkSubtract @Positive @Signed
      it "A = Positive, B = Integer" $ hedgehog $ checkSubtract @Positive @Integer

  context "(-) in A behaves the same as (-) in Integer if the result is in A, undefined otherwise" do
    modifyMaxSuccess (\_ -> 1000) do
      it "A = Positive" $ hedgehog $ checkNumMinus @Positive

checkSubtract ::
  forall a b m.
  (GenIntegral a, Subtraction a, Subtraction' b, Num b) =>
  (IntegerConvert a b, IntegerNarrow b a) =>
  (Eq b, Show b) =>
  MonadCatch m =>
  PropertyT m ()
checkSubtract = do
  x :: a <- forAll Gen.integral
  y :: a <- forAll Gen.integral
  (subtract x y :: b) === (convert x - convert y :: b)

checkNumMinus ::
  forall a m.
  (GenIntegral a, Subtraction a, IntegerNarrow Integer a) =>
  MonadCatch m =>
  PropertyT m ()
checkNumMinus = do
  x :: a <- forAll Gen.integral
  y :: a <- forAll Gen.integral
  case narrow (toInteger x - toInteger y) :: Maybe a of
    Just z -> x - y === z
    Nothing -> do
      z <- try (pure $! x - y)
      z === Either.Left Underflow