packages feed

fb-util-0.1.0.1: Util/HUnit.hs

{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

module Util.HUnit
  ( absError
  , assertAbsError
  , assertElem
  , assertSubsetOf
  , assertNotElem
  , assertPermutationOf
  , assertThrow
  ) where

import Control.Exception
import Data.Either (isLeft)
import Data.Foldable (toList)
import Data.List
import Test.HUnit


-- | The absolute error between an expected and actual value.
absError :: (Fractional a) => a -> a -> a
absError expected actual = abs (expected - actual)

-- | Asserts that the absolute error of a value is within a given
-- range. If you want the range to be in the realm of machine epsilon,
-- use @Data.AEq@ instead.
assertAbsError
  :: (Fractional a, Ord a, Show a)
  => String
  -- ^ Error message.
  -> a
  -- ^ Expected value.
  -> a
  -- ^ Error range (e.g., @1e-3@).
  -> a
  -- ^ Actual value.
  -> Assertion
assertAbsError message expected epsilon actual
  = flip assertBool (absError expected actual <= epsilon) $ concat
  [ message
  , " (expected "
  , show expected
  , "+/-"
  , show epsilon
  , " but got "
  , show actual
  , ")"
  ]

assertPermutationOf
  :: (Ord a, Show a)
  => String -> [a] -> [a] -> Assertion
assertPermutationOf m as bs = assertEqual m (sort as) (sort bs)

assertElem :: (Eq a, Show a, Foldable t) => String -> a -> t a -> Assertion
assertElem message expected actual =
  flip assertBool (expected `elem` actual) $ concat
    [ message
    , " (expected "
    , show expected
    , " to occur in "
    , show $ toList actual
    , ")"
    ]

assertSubsetOf
  :: (Eq a, Show a, Foldable t, Foldable f)
  => String
  -> f a
  -> t a
  -> Assertion
assertSubsetOf message expected actual =
  flip assertBool (all (`elem` actual) expected) $ concat
    [ message
    , " (expected all of"
    , show $ toList expected
    , " to occur in "
    , show $ toList actual
    , ")"
    ]

assertNotElem :: (Eq a, Show a, Foldable t) => String -> a -> t a -> Assertion
assertNotElem message expected actual =
  flip assertBool (not $ expected `elem` actual) $ concat
    [ message
    , " (expected "
    , show expected
    , " to not occur in "
    , show $ toList actual
    , ")"
    ]

assertThrow :: (Show a) => String -> IO a -> Assertion
assertThrow message test = do
  (actual :: Either SomeException a) <- try test
  flip assertBool (isLeft actual) $ concat
    [ message
    , " (expected: exception thrown; but got: "
    , show actual
    , ")"
    ]