packages feed

bv-little-1.3.0: test/TestSuite.hs

{-|

Copyright   : © 2020 Alex Washburn
License     : BSD-3-Clause
Maintainer  : github@recursion.ninja
Stability   : Stable

-}

{-# Language FlexibleInstances #-}
{-# Language ImportQualifiedPost #-}

  -- We apply this to suppress the deprecated warning cause by calls to 'bitSize'
  -- If there is a more fine-grained way to suppress this warning without suppressing
  -- deprecated warnings for the whole module, that should be done instead.
{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}

module Main
    ( main
    ) where

import Control.DeepSeq
import Data.BitVector.LittleEndian
import Data.BitVector.LittleEndian.Instances ()
import Data.BitVector.Visual
import Data.Bits
import Data.Foldable
import Data.Functor.Compose
import Data.Functor.Identity
import Data.Hashable
import Data.Maybe
import Data.MonoTraversable
import Data.MonoTraversable.Keys
import Data.Monoid ()
import Data.Semigroup
import GHC.Exts (IsList(..))
import Operator.Binary.Comparison
import Operator.Binary.Logical
import Operator.Unary.Logical
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck hiding (forAll, testProperty, (.&.))
import Test.Tasty.QuickCheck qualified as QC
import Test.Tasty.SmallCheck hiding (Property, testProperty, (==>))
import Test.Tasty.SmallCheck qualified as SC
import TextShow (TextShow(showb), toString)


infix 0 -=>
(-=>) :: QC.Testable p => Bool -> p -> Property
(-=>) p q = not p .||. q


{-|
Complete test suite for the 'BitVector' type.
-}
main :: IO ()
main = defaultMain testSuite


testSuite :: TestTree
testSuite = testGroup
    "BitVector tests"
    [ bitsTests
    , finiteBitsTests
    , hashableTests
    , monoAdjustableProperties
    , monoFunctorProperties
    , monoFoldableProperties
    , monoFoldableWithKeyProperties
    , monoKeyedProperties
    , monoTraversableProperties
    , monoTraversableWithKeyProperties
    , monoZipProperties
    , monoZipWithKeyProperties
    , monoidProperties
    , normalFormDataProperties
    , orderingProperties
    , semigroupProperties
    , showProperties
    , textshowProperties
    , bitVectorProperties
    , bitVectorRankSelect
    , monoFunctorEquivalence
    , monoFoldableEquivalence
    , monoZipEquivalence
    ]


bitsTests :: TestTree
bitsTests = testGroup
    "Properties of Bits"
    [ QC.testProperty "∀ i ≥ 0, clearBit zeroBits i === zeroBits" zeroBitsAndClearBit
    , QC.testProperty "∀ i ≥ 0, setBit   zeroBits i === bit i" zeroBitsAndSetBit
    , QC.testProperty "∀ i ≥ 0, testBit  zeroBits i === False" zeroBitsAndTestBit
    , testCase "         popCount zeroBits   === 0" zeroBitsAndPopCount
    , QC.testProperty "complement === omap not" complementOmapNot
    , QC.testProperty "(`setBit` i) === (.|. bit i)" setBitDefinition
    , QC.testProperty "(`clearBit` i) === (.&. complement (bit i))" clearBitDefinition
    , QC.testProperty "(`complementBit` i) === (`xor` bit i)" complementBitDefinition
    , QC.testProperty "(`testBit` i) . (`setBit` n)" testBitAndSetBit
    , QC.testProperty "not  . (`testBit` i) . (`clearBit` i)" testBitAndClearBit
    , QC.testProperty "(`shiftL`  i) === (`shift`   i)" leftShiftPositiveShift
    , QC.testProperty "(`shiftR`  i) === (`shift`  -i)" rightShiftNegativeShift
    , QC.testProperty "(`rotateL` i) === (`rotate`  i)" leftRotatePositiveRotate
    , QC.testProperty "(`rotateR` i) === (`rotate` -i)" rightRotateNegativeRotate
    , QC.testProperty "(`rotateR` i) . (`rotateL` i) === id" leftRightRotateIdentity
    , QC.testProperty "(`rotateL` i) . (`rotateR` i) === id" rightLeftRotateIdentity
    ]
    where
        zeroBitsAndClearBit :: NonNegative Int -> Property
        zeroBitsAndClearBit (NonNegative i) = clearBit (zeroBits :: BitVector) i === zeroBits

        zeroBitsAndSetBit :: NonNegative Int -> Property
        zeroBitsAndSetBit (NonNegative i) = setBit (zeroBits :: BitVector) i === bit i

        zeroBitsAndTestBit :: NonNegative Int -> Property
        zeroBitsAndTestBit (NonNegative i) = testBit (zeroBits :: BitVector) i === False

        zeroBitsAndPopCount :: Assertion
        zeroBitsAndPopCount = popCount (zeroBits :: BitVector) @?= 0

        complementOmapNot :: BitVector -> Property
        complementOmapNot bv = complement bv === omap not bv

        setBitDefinition :: NonNegative Int -> BitVector -> Property
        setBitDefinition (NonNegative i) bv = bv `setBit` i === bv .|. bit i

        clearBitDefinition :: NonNegative Int -> BitVector -> Property
        clearBitDefinition (NonNegative i) bv =
            i < (fromEnum . dimension) bv -=> (bv `clearBit` i === bv .&. complement (zed .|. bit i))
            where zed = fromNumber (dimension bv) (0 :: Integer)

        complementBitDefinition :: NonNegative Int -> BitVector -> Property
        complementBitDefinition (NonNegative i) bv = bv `complementBit` i === bv `xor` bit i

        testBitAndSetBit :: NonNegative Int -> BitVector -> Bool
        testBitAndSetBit (NonNegative i) = (`testBit` i) . (`setBit` i)

        testBitAndClearBit :: NonNegative Int -> BitVector -> Bool
        testBitAndClearBit (NonNegative i) = not . (`testBit` i) . (`clearBit` i)

        leftShiftPositiveShift :: NonNegative Int -> BitVector -> Property
        leftShiftPositiveShift (NonNegative i) bv = bv `shiftL` i === bv `shift` i

        rightShiftNegativeShift :: NonNegative Int -> BitVector -> Property
        rightShiftNegativeShift (NonNegative i) bv = bv `shiftR` i === bv `shift` (-i)

        leftRotatePositiveRotate :: NonNegative Int -> BitVector -> Property
        leftRotatePositiveRotate (NonNegative i) bv = bv `rotateL` i === bv `rotate` i

        rightRotateNegativeRotate :: NonNegative Int -> BitVector -> Property
        rightRotateNegativeRotate (NonNegative i) bv = bv `rotateR` i === bv `rotate` (-i)

        leftRightRotateIdentity :: NonNegative Int -> BitVector -> Property
        leftRightRotateIdentity (NonNegative i) bv = ((`rotateR` i) . (`rotateL` i)) bv === bv

        rightLeftRotateIdentity :: NonNegative Int -> BitVector -> Property
        rightLeftRotateIdentity (NonNegative i) bv = ((`rotateL` i) . (`rotateR` i)) bv === bv


finiteBitsTests :: TestTree
finiteBitsTests = testGroup
    "Properties of FiniteBits"
    [ QC.testProperty "bitSize === finiteBitSize" finiteBitSizeIsBitSize
    , QC.testProperty "bitSizeMaybe === Just . finiteBitSize" finiteBitSizeIsBitSizeMaybe
    , QC.testProperty "dimension === finiteBitSize" finiteBitSizeIsDimension
    , QC.testProperty "countLeadingZeros <= finiteBitSize" finiteBitSizeIsGreaterThanLeadingZeros
    , QC.testProperty "countTrailingZeros <= finiteBitSize" finiteBitSizeIsGreaterThanTrailingZeros
    , QC.testProperty "length . toBits === finiteBitSize" finiteBitSizeIsBitLength
    , QC.testProperty "length . takeWhile not === countLeadingZeros . fromBits" countLeadingZeroAndFromBits
    , QC.testProperty "length . takeWhile not . toBits === countLeadingZeros" countLeadingZeroAndToBits
    , QC.testProperty
        "length . takeWhile not . reverse === countTrailingZeros . fromBits"
        countTrailingZeroAndFromBits
    , QC.testProperty
        "length . takeWhile not . reverse . toBits === countTrailingZeros"
        countTrailingZeroAndToBits
    ]
    where
        finiteBitSizeIsBitSize :: BitVector -> Property
        finiteBitSizeIsBitSize bv = bitSize bv === finiteBitSize bv

        finiteBitSizeIsBitSizeMaybe :: BitVector -> Property
        finiteBitSizeIsBitSizeMaybe bv = bitSizeMaybe bv === (Just . finiteBitSize) bv

        finiteBitSizeIsDimension :: BitVector -> Property
        finiteBitSizeIsDimension bv = (fromEnum . dimension) bv === finiteBitSize bv

        finiteBitSizeIsGreaterThanLeadingZeros :: BitVector -> Bool
        finiteBitSizeIsGreaterThanLeadingZeros bv = countLeadingZeros bv <= finiteBitSize bv

        finiteBitSizeIsGreaterThanTrailingZeros :: BitVector -> Bool
        finiteBitSizeIsGreaterThanTrailingZeros bv = countTrailingZeros bv <= finiteBitSize bv

        finiteBitSizeIsBitLength :: BitVector -> Property
        finiteBitSizeIsBitLength bv = (length . toBits) bv === finiteBitSize bv

        countLeadingZeroAndFromBits :: [Bool] -> Property
        countLeadingZeroAndFromBits bs = (length . takeWhile not) bs === (countLeadingZeros . fromBits) bs

        countLeadingZeroAndToBits :: BitVector -> Property
        countLeadingZeroAndToBits bv = (length . takeWhile not . toBits) bv === countLeadingZeros bv

        countTrailingZeroAndFromBits :: [Bool] -> Property
        countTrailingZeroAndFromBits bs =
            (length . takeWhile not . reverse) bs === (countTrailingZeros . fromBits) bs

        countTrailingZeroAndToBits :: BitVector -> Property
        countTrailingZeroAndToBits bv =
            (length . takeWhile not . reverse . toBits) bv === countTrailingZeros bv


hashableTests :: TestTree
hashableTests = testGroup
    "Properties of Hashable"
    [ localOption (QuickCheckTests 10000)
        $ QC.testProperty "a == b -=> (hashWithSalt a) === (hashWithSalt b)" differentSaltsDifferentHashes
    ]
    where
        differentSaltsDifferentHashes :: BitVector -> Int -> Int -> Property
        differentSaltsDifferentHashes bv salt1 salt2 =
            salt1 /= salt2 -=> hashWithSalt salt1 bv /= hashWithSalt salt2 bv


monoAdjustableProperties :: TestTree
monoAdjustableProperties = testGroup
    "Properties of a MonoAdjustable"
    [ QC.testProperty "oadjust id k === id" oadjustId
    , QC.testProperty "oadjust (f . g) k === oadjust f k . oadjust g k" oadjustComposition
    , QC.testProperty "oadjust f k === omapWithKey (\\i -> if i == k then f else id)" omapConditionality
    , QC.testProperty "oreplace k v === oreplace k v . oadjust f k" oreplaceNullification
    , QC.testProperty "oreplace (f v) k === oadjust f k . oreplace k v" oreplaceApplication
    ]
    where
        oadjustId :: Word -> BitVector -> Property
        oadjustId k bv = oadjust id k bv === bv

        oadjustComposition :: Blind (Bool -> Bool) -> Blind (Bool -> Bool) -> Word -> BitVector -> Property
        oadjustComposition (Blind f) (Blind g) k bv = oadjust (f . g) k bv === (oadjust f k . oadjust g k) bv

        omapConditionality :: Blind (Bool -> Bool) -> Word -> BitVector -> Property
        omapConditionality (Blind f) k bv =
            oadjust f k bv === omapWithKey (\i -> if i == k then f else id) bv

        oreplaceNullification :: Blind (Bool -> Bool) -> Word -> Bool -> BitVector -> Property
        oreplaceNullification (Blind f) k v bv = oreplace k v bv === (oreplace k v . oadjust f k) bv

        oreplaceApplication :: Blind (Bool -> Bool) -> Word -> Bool -> BitVector -> Property
        oreplaceApplication (Blind f) k v bv = oreplace k (f v) bv === (oadjust f k . oreplace k v) bv


monoFunctorProperties :: TestTree
monoFunctorProperties = testGroup
    "Properties of a MonoFunctor"
    [ QC.testProperty "omap id === id" omapId
    , QC.testProperty "omap (f . g)  === omap f . omap g" omapComposition
    ]
    where
        omapId :: BitVector -> Property
        omapId bv = omap id bv === bv

        omapComposition :: Blind (Bool -> Bool) -> Blind (Bool -> Bool) -> BitVector -> Property
        omapComposition (Blind f) (Blind g) bv = omap (f . g) bv === (omap f . omap g) bv


monoFoldableProperties :: TestTree
monoFoldableProperties = testGroup
    "Properties of MonoFoldable"
    [ QC.testProperty "ofoldr f z t === appEndo (ofoldMap (Endo . f) t ) z" testFoldrFoldMap
    , QC.testProperty
        "ofoldl' f z t === appEndo (getDual (ofoldMap (Dual . Endo . flip f) t)) z"
        testFoldlFoldMap
    , QC.testProperty "ofoldr f z === ofoldr f z . otoList" testFoldr
    , QC.testProperty "ofoldl' f z === ofoldl' f z . otoList" testFoldl
    , QC.testProperty "ofoldr1Ex f === foldr1 f . otoList" testFoldr1
    , QC.testProperty "ofoldl1Ex' f === foldl1 f . otoList" testFoldl1
    , QC.testProperty "oall f === getAll . ofoldMap (All . f)" testAll
    , QC.testProperty "oany f === getAny . ofoldMap (Any . f)" testAny
    , QC.testProperty "olength === length . otoList" testLength
    , QC.testProperty "onull === (0 ==) . olength" testNull
    , QC.testProperty "headEx === getFirst . ofoldMap1Ex First" testHead
    , QC.testProperty "lastEx === getLast . ofoldMap1Ex Last" testTail
    , QC.testProperty "oelem e /== onotElem e" testInclusionConsistency
    ]
    where
        testFoldrFoldMap :: Blind (Bool -> Word -> Word) -> Word -> BitVector -> Property
        testFoldrFoldMap (Blind f) z bv = ofoldr f z bv === appEndo (ofoldMap (Endo . f) bv) z

        testFoldlFoldMap :: Blind (Word -> Bool -> Word) -> Word -> BitVector -> Property
        testFoldlFoldMap (Blind f) z bv =
            ofoldl' f z bv === appEndo (getDual (ofoldMap (Dual . Endo . flip f) bv)) z

        testFoldr :: Blind (Bool -> Word -> Word) -> Word -> BitVector -> Property
        testFoldr (Blind f) z bv = ofoldr f z bv === (ofoldr f z . otoList) bv

        testFoldl :: Blind (Word -> Bool -> Word) -> Word -> BitVector -> Property
        testFoldl (Blind f) z bv = ofoldl' f z bv === (ofoldl' f z . otoList) bv

    --    testFoldr1 :: Blind (Bool -> Bool -> Bool) -> BitVector -> Property
        testFoldr1 :: BinaryLogicalOperator -> BitVector -> Property
    --    testFoldr1 (Blind f) bv =
        testFoldr1 x bv = (not . onull) bv -=> ofoldr1Ex f bv === (foldr1 f . otoList) bv
            where f = getBinaryLogicalOperator x

        testFoldl1 :: Blind (Bool -> Bool -> Bool) -> BitVector -> Property
        testFoldl1 (Blind f) bv = (not . onull) bv -=> ofoldl1Ex' f bv === (foldl1 f . otoList) bv

        testAll :: Blind (Bool -> Bool) -> BitVector -> Property
        testAll (Blind f) bv = oall f bv === (getAll . ofoldMap (All . f)) bv

        testAny :: Blind (Bool -> Bool) -> BitVector -> Property
        testAny (Blind f) bv = oany f bv === (getAny . ofoldMap (Any . f)) bv

        testLength :: BitVector -> Property
        testLength bv = olength bv === (length . otoList) bv

        testNull :: BitVector -> Property
        testNull bv = onull bv === ((0 ==) . olength) bv

        testHead :: BitVector -> Property
        testHead bv = (not . onull) bv -=> headEx bv === (getFirst . ofoldMap1Ex First) bv

        testTail :: BitVector -> Property
        testTail bv = (not . onull) bv -=> lastEx bv === (getLast . ofoldMap1Ex Last) bv

        testInclusionConsistency :: (Bool, BitVector) -> Property
        testInclusionConsistency (e, bv) = oelem e bv === (not . onotElem e) bv


monoFoldableWithKeyProperties :: TestTree
monoFoldableWithKeyProperties = testGroup
    "Properties of MonoFoldableWithKey"
    [ QC.testProperty "otoKeyedList === zip [0..] . otoList" testNaturalKeyedList
    , QC.testProperty "ofoldMapWithKey (const f) === ofoldMap f" testConstantFoldMap
    , QC.testProperty "ofoldrWithKey (const f) === ofoldr f" testConstantFoldr
    , QC.testProperty "ofoldlWithKey (const . f) === ofoldl f" testConstantFoldl
    , QC.testProperty "ofoldMapWithKey f === foldMap (uncurry f) . otoKeyedList" testUncurriedFoldMap
    , QC.testProperty "ofoldrWithKey f === foldr (uncurry f) . otoKeyedList" testUncurriedFoldr
    , QC.testProperty "ofoldlWithKey f === foldl (uncurry . f) . otoKeyedList" testUncurriedFoldl
    ]
    where
        testNaturalKeyedList :: BitVector -> Property
        testNaturalKeyedList bv = otoKeyedList bv === (zip [0 ..] . otoList) bv

        testConstantFoldMap :: Blind (Bool -> [Word]) -> BitVector -> Property
        testConstantFoldMap (Blind f) bv = ofoldMapWithKey (const f) bv === ofoldMap f bv

        testConstantFoldr :: Blind (Bool -> Word -> Word) -> Word -> BitVector -> Property
        testConstantFoldr (Blind f) e bv = ofoldrWithKey (const f) e bv === ofoldr f e bv

        testConstantFoldl :: Blind (Word -> Bool -> Word) -> Word -> BitVector -> Property
        testConstantFoldl (Blind f) e bv = ofoldlWithKey (const . f) e bv === ofoldl' f e bv

        testUncurriedFoldMap :: Blind (Word -> Bool -> [Word]) -> BitVector -> Property
        testUncurriedFoldMap (Blind f) bv = ofoldMapWithKey f bv === (foldMap (uncurry f) . otoKeyedList) bv

        testUncurriedFoldr :: Blind (Word -> Bool -> Word -> Word) -> Word -> BitVector -> Property
        testUncurriedFoldr (Blind f) e bv = ofoldrWithKey f e bv === (foldr (uncurry f) e . otoKeyedList) bv

        testUncurriedFoldl :: Blind (Word -> Word -> Bool -> Word) -> Word -> BitVector -> Property
        testUncurriedFoldl (Blind f) e bv =
            ofoldlWithKey f e bv === (foldl (uncurry . f) e . otoKeyedList) bv


monoKeyedProperties :: TestTree
monoKeyedProperties = testGroup
    "Properties of a MonoKeyed"
    [ QC.testProperty "omapWithKey (const id) === id" omapId
    , QC.testProperty "omapWithKey (\\k -> f k . g k)  === omapWithKey f . omapWithKey g" omapComposition
    ]
    where
        omapId :: BitVector -> Property
        omapId bv = omapWithKey (const id) bv === bv

        omapComposition
            :: Blind (Word -> Bool -> Bool) -> Blind (Word -> Bool -> Bool) -> BitVector -> Property
        omapComposition (Blind f) (Blind g) bv =
            omapWithKey (\k -> f k . g k) bv === (omapWithKey f . omapWithKey g) bv


monoTraversableProperties :: TestTree
monoTraversableProperties = testGroup
    "Properties of MonoTraversable"
    [ QC.testProperty "t . otraverse f === otraverse (t . f)" testNaturality
    , QC.testProperty "otraverse Identity === Identity" testIdentity
    , QC.testProperty
        "otraverse (Compose . fmap g . f) === Compose . fmap (otraverse g) . otraverse f"
        testComposition
    , QC.testProperty "otraverse === omapM" testDefinitionEquality
    ]
    where
        testNaturality :: Blind (Bool -> [Bool]) -> BitVector -> Property
        testNaturality (Blind f) bv = (headMay . otraverse f) bv === otraverse (headMay . f) bv

        testIdentity :: BitVector -> Property
        testIdentity bv = otraverse Identity bv === Identity bv

        testComposition
            :: Blind (Bool -> Either Word Bool) -> Blind (Bool -> Maybe Bool) -> BitVector -> Property
        testComposition (Blind f) (Blind g) bv =
            otraverse (Compose . fmap g . f) bv === (Compose . fmap (otraverse g) . otraverse f) bv

        testDefinitionEquality :: Blind (Bool -> Maybe Bool) -> BitVector -> Property
        testDefinitionEquality (Blind f) bv = otraverse f bv === omapM f bv


monoTraversableWithKeyProperties :: TestTree
monoTraversableWithKeyProperties = testGroup
    "Properties of MonoTraversableWithKey"
    [ QC.testProperty "t . otraverseWithKey f === otraverseWithKey (\\k -> t . f k)" testNaturality
    , QC.testProperty "otraverseWithKey (const Identity) === Identity" testIdentity
    , QC.testProperty
        "otraverseWithKey (\\k -> Compose . fmap (g k) . f k) === Compose . fmap (otraverseWithKey g) . otraverseWithKey f"
        testComposition
    , QC.testProperty "otraverseWithKey === omapWithKeyM" testDefinitionEquality
    ]
    where
        testNaturality :: Blind (Word -> Bool -> [Bool]) -> BitVector -> Property
        testNaturality (Blind f) bv =
            (headMay . otraverseWithKey f) bv === otraverseWithKey (\k -> headMay . f k) bv

        testIdentity :: BitVector -> Property
        testIdentity bv = otraverseWithKey (const Identity) bv === Identity bv

        testComposition
            :: Blind (Word -> Bool -> Either Word Bool)
            -> Blind (Word -> Bool -> Maybe Bool)
            -> BitVector
            -> Property
        testComposition (Blind f) (Blind g) bv = otraverseWithKey (\k -> Compose . fmap (g k) . f k) bv
            === (Compose . fmap (otraverseWithKey g) . otraverseWithKey f) bv

        testDefinitionEquality :: Blind (Word -> Bool -> Maybe Bool) -> BitVector -> Property
        testDefinitionEquality (Blind f) bv = otraverseWithKey f bv === omapWithKeyM f bv


monoZipProperties :: TestTree
monoZipProperties = testGroup
    "Properties of a MonoZip"
    [ QC.testProperty "ozipWith const u u === ozipWith (const id) u u === u" ozipWithConst
    , QC.testProperty "ozipWith (flip f) x y === ozipWith f y x" ozipWithTransposition
    , QC.testProperty
        "ozipWith (\\a b -> f (g a) (h b)) x y === ozipWith f (omap g x) (omap h y)"
        ozipWithComposition
    ]
    where
        ozipWithConst :: BitVector -> Property
        ozipWithConst u = ozipWith const u u === u .&&. ozipWith (const id) u u === u

        ozipWithTransposition :: Blind (Bool -> Bool -> Bool) -> BitVector -> BitVector -> Property
        ozipWithTransposition (Blind f) x y = ozipWith (flip f) x y === ozipWith f y x

        ozipWithComposition
            :: Blind (Bool -> Bool -> Bool)
            -> Blind (Bool -> Bool)
            -> Blind (Bool -> Bool)
            -> BitVector
            -> BitVector
            -> Property
        ozipWithComposition (Blind f) (Blind g) (Blind h) x y =
            ozipWith (\a b -> f (g a) (h b)) x y === ozipWith f (omap g x) (omap h y)


monoZipWithKeyProperties :: TestTree
monoZipWithKeyProperties = testGroup
    "Properties of a MonoZipWithKey"
    [QC.testProperty "ozipWithKey (const f) === ozipWith f" ozipWithKeyConst]
    where
        ozipWithKeyConst :: Blind (Bool -> Bool -> Bool) -> BitVector -> BitVector -> Property
        ozipWithKeyConst (Blind f) x y = ozipWithKey (const f) x y === ozipWith f x y


{- HLINT ignore monoidProperties "Monoid law, left identity" -}
{- HLINT ignore monoidProperties "Monoid law, right identity" -}
{- HLINT ignore monoidProperties "Use fold" -}
monoidProperties :: TestTree
monoidProperties = testGroup
    "Properties of a Monoid"
    [ QC.testProperty "left identity" leftIdentity
    , QC.testProperty "right identity" rightIdentity
    , QC.testProperty "mempty is associative" operationAssociativity
    , QC.testProperty "mconcat === foldr (<>) mempty" foldableApplication
    ]
    where
        leftIdentity :: BitVector -> Property
        leftIdentity a = mempty `mappend` a === a

        rightIdentity :: BitVector -> Property
        rightIdentity a = a `mappend` mempty === a

        operationAssociativity :: BitVector -> BitVector -> BitVector -> Property
        operationAssociativity a b c = a `mappend` (b `mappend` c) === (a `mappend` b) `mappend` c

        foldableApplication :: [BitVector] -> Property
        foldableApplication bvs = mconcat bvs === foldr mappend mempty bvs


normalFormDataProperties :: TestTree
normalFormDataProperties = testGroup
    "Properties of NFData"
    [QC.testProperty "rnf result is finite" finiteReduction]
    where
        finiteReduction :: BitVector -> Property
        finiteReduction bv = rnf bv === ()


orderingProperties :: TestTree
orderingProperties = testGroup
    "Properties of an Ordering"
    [ QC.testProperty "ordering preserves symmetry" symmetry
    , QC.testProperty "ordering is transitive (total)" transitivity
    ]
    where
        symmetry :: BitVector -> BitVector -> Bool
        symmetry lhs rhs = case (lhs `compare` rhs, rhs `compare` lhs) of
            (EQ, EQ) -> True
            (GT, LT) -> True
            (LT, GT) -> True
            _        -> False

        transitivity :: BitVector -> BitVector -> BitVector -> Property
        transitivity a b c = caseOne .||. caseTwo
            where
                caseOne = (a <= b && b <= c) -=> a <= c
                caseTwo = (a >= b && b >= c) -=> a >= c


semigroupProperties :: TestTree
semigroupProperties = testGroup
    "Properties of a Semigroup"
    [ localOption (QuickCheckTests 10000) $ QC.testProperty "(<>) is associative" operationAssociativity
    , QC.testProperty "sconcat === foldr1 (<>)" foldableApplication
    , QC.testProperty "stimes n === mconcat . replicate n" repeatedApplication
    ]
    where
        operationAssociativity :: BitVector -> BitVector -> BitVector -> Property
        operationAssociativity a b c = a <> (b <> c) === (a <> b) <> c

        foldableApplication :: NonEmptyList BitVector -> Property
        foldableApplication manyBVs = sconcat bvs === foldr1 mappend bvs
            where
            -- We do this because there is currently no Arbitrary instance for NonEmpty
                  bvs = fromList $ getNonEmpty manyBVs

        repeatedApplication :: NonNegative Int -> BitVector -> Property
        repeatedApplication (NonNegative i) bv = stimes i bv === (mconcat . replicate i) bv


showProperties :: TestTree
showProperties = testGroup
    "Properties of Show"
    [ QC.testProperty "show result is finite" finiteString
    , QC.testProperty "show result is non-null" nonNullString
    ]
    where
        finiteString :: BitVector -> Property
        finiteString bv = show bv === show bv

        nonNullString :: BitVector -> Bool
        nonNullString = not . null . show



textshowProperties :: TestTree
textshowProperties = testGroup "Properties of TextShow"
    [ QC.testProperty "textshow and show result agree" textshowCoherence
    ]
  where
    textshowCoherence :: BitVector -> Property
    textshowCoherence bv =
        (toString . showb $ bv) === show bv


bitVectorProperties :: TestTree
bitVectorProperties = testGroup
    "BitVector properties"
    [ QC.testProperty "otoList === toBits" otoListTest
    , QC.testProperty "dimension === length . toBits" dimensionAndToBits
    , QC.testProperty "dimension === finiteBitSize" dimensionAndFiniteBitSize
    , QC.testProperty "fromBits . toBits === id" toBitsFromBits
    , testCase "isZeroVector zeroBits" zeroBitsIsZeroVector
    , QC.testProperty "isZeroVector === (0 ==) . popCount" popCountAndZeroVector
    , QC.testProperty "isZeroVector === all not . toBits" zeroVectorAndAllBitsOff
    , QC.testProperty "(0 ==) . toUnsignedNumber -=> isZeroVector" toUnsignedNumImpliesZeroVector
    , QC.testProperty "toSignedNumber . fromNumber === id" bitVectorUnsignedNumIdentity
    , QC.testProperty "isSigned == const False" noSignBitVector
-- For an unknown reason, this test case causes GHC to panic!
    , QC.testProperty "i >  j -=> subRange (i,j) === const zeroBits" badSubRangeEmptyResult
    , QC.testProperty "i <= j -=> dimension . subRange (i,j) === const (j - i + 1)" subRangeFixedDimension
    ]
    where
        otoListTest :: BitVector -> Property
        otoListTest bv = otoList bv === toBits bv

        dimensionAndToBits :: BitVector -> Property
        dimensionAndToBits bv = (fromEnum . dimension) bv === (length . toBits) bv

        dimensionAndFiniteBitSize :: BitVector -> Property
        dimensionAndFiniteBitSize bv = (fromEnum . dimension) bv === finiteBitSize bv

        toBitsFromBits :: BitVector -> Property
        toBitsFromBits bv = (fromBits . toBits) bv === bv

        zeroBitsIsZeroVector :: Assertion
        zeroBitsIsZeroVector = assertBool "zeroBits is not a 'zero vector'" $ isZeroVector zeroBits

        popCountAndZeroVector :: BitVector -> Property
        popCountAndZeroVector bv = isZeroVector bv === ((0 ==) . popCount) bv

        zeroVectorAndAllBitsOff :: BitVector -> Property
        zeroVectorAndAllBitsOff bv = isZeroVector bv === (all not . toBits) bv

        toUnsignedNumImpliesZeroVector :: BitVector -> Property
        toUnsignedNumImpliesZeroVector bv =
            ((0 ==) . (toUnsignedNumber :: BitVector -> Integer)) bv -=> isZeroVector bv

        bitVectorUnsignedNumIdentity :: Integer -> Property
        bitVectorUnsignedNumIdentity num = (toSignedNumber . fromNumber width) num === num
            where width = succ . succ . ceiling . logBase (2.0 :: Double) . fromIntegral $ abs num

        noSignBitVector :: BitVector -> Property
        noSignBitVector bv = isSigned bv === False

        badSubRangeEmptyResult :: (Word, Word) -> BitVector -> Property
        badSubRangeEmptyResult range@(lower, upper) bv = lower > upper -=> subRange range bv === zeroBits

        subRangeFixedDimension :: (NonNegative Int, NonNegative Int) -> BitVector -> Property
        subRangeFixedDimension (NonNegative lowerI, NonNegative upperI) bv =
            lower <= upper -=> dimension (subRange (lower, upper) bv) === upper - lower + 1
            where
                lower = toEnum lowerI
                upper = toEnum upperI


bitVectorRankSelect :: TestTree
bitVectorRankSelect = testGroup
    "BitVector rank/select"
    [ QC.testProperty "select (bit i) 0 === i" selectBitValue
    , QC.testProperty "select (bit x .|. bit y) 0 === min (select (bit x) 0) (select (bit y) 0)" selectBitOr
    , QC.testProperty
        "rank (bit x .|. bit y) (max x y + 1) === rank (bit x) (x+1) + rank (bit y) (y+1)"
        rankBitOr
    , QC.testProperty "rank (bit i) (i+1) === i" rankBitValue
    , QC.testProperty "rank <$> id <*> dimension === popCount" rankPopCount
    , QC.testProperty "rank bv i === length . filter id . take i . toBits bv" rankToBits
    , QC.testProperty "rank bv (select bv i) === i" rankSelectMinDef
    ]
    where
        selectBitValue :: NonNegative Int -> Property
        selectBitValue (NonNegative x) = select (bit x) 0 === Just (toEnum x)

        selectBitOr :: NonNegative Int -> NonNegative Int -> Property
        selectBitOr (NonNegative x) (NonNegative y) =
            select (bit x .|. bit y) 0 === min (select (bit x) 0) (select (bit y) 0)

        rankBitValue :: NonNegative Word -> Property
        rankBitValue (NonNegative x) = rank (bit (fromEnum x)) (x + 1) === 1

        rankBitOr :: NonNegative Int -> NonNegative Int -> Property
        rankBitOr (NonNegative x) (NonNegative y) =
            x /= y -=> rank (bit x .|. bit y) z' === rank (bit x) (x' + 1) + rank (bit y) (y' + 1)
            where
                x' = toEnum x
                y' = toEnum y
                z' = max x' y' + 1

        rankPopCount :: BitVector -> Property
        rankPopCount bv = (rank <$> id <*> dimension) bv === toEnum (popCount bv)

        rankToBits :: BitVector -> NonNegative Word -> Property
        rankToBits bv (NonNegative x) =
            rank bv x === (toEnum . length . filter id . take (fromEnum x) . toBits) bv

        rankSelectMinDef :: BitVector -> NonNegative Word -> Property
        rankSelectMinDef bv (NonNegative x) =
            let idx = select bv x
                k   = fromJust idx
            in  idx === Nothing .||. rank bv k === x


monoFunctorEquivalence :: TestTree
monoFunctorEquivalence = testGroup
    "Equivalence of a MonoFunctor"
    [SC.testProperty "omap f === fromBits . map f . toBits" $ forAll omapOptimizationIsValid]
    where
        omapOptimizationIsValid :: (Bool -> Bool, VisualBitVector) -> Bool
        omapOptimizationIsValid (f, y) = omap f bv == (fromBits . map f . toBits) bv
            where bv = getBitVector y


monoFoldableEquivalence :: TestTree
monoFoldableEquivalence = testGroup
    "Equivalence of a MonoFoldable"
    [ SC.testProperty "oall f === all f . otoList" $ forAll oallOptimizationIsValid
    , SC.testProperty "oany f === any f . otoList" $ forAll oanyOptimizationIsValid
    , SC.testProperty "ofoldr1Ex  f === foldr1 f . otoList" $ forAll ofoldr1ExOptimizationIsValid
    , SC.testProperty "ofoldl1Ex' f === foldl1 f . otoList" $ forAll ofoldl1ExOptimizationIsValid
    , SC.testProperty "headEx === head . otoList" $ forAll headExOptimizationIsValid
    , SC.testProperty "lastEx === last . otoList" $ forAll lastExOptimizationIsValid
    , SC.testProperty "maximumByEx f === maximumBy f . otoList" $ forAll maximumByExOptimizationIsValid
    , SC.testProperty "minimumByEx f === minimumBy f . otoList" $ forAll minimumByExOptimizationIsValid
    , SC.testProperty "oelem e === oelem e . otoList" $ forAll oelemOptimizationIsValid
    , SC.testProperty "onotElem e === onotElem e . otoList" $ forAll onotElemOptimizationIsValid
    ]
    where
        oallOptimizationIsValid :: (UnaryLogicalOperator, VisualBitVector) -> Bool
        oallOptimizationIsValid (y, x) = oall op bv == (all op . otoList) bv
            where
                bv = getBitVector x
                op = getUnaryLogicalOperator y

        oanyOptimizationIsValid :: (UnaryLogicalOperator, VisualBitVector) -> Bool
        oanyOptimizationIsValid (y, x) = oany op bv == (any op . otoList) bv
            where
                bv = getBitVector x
                op = getUnaryLogicalOperator y

        ofoldr1ExOptimizationIsValid :: (BinaryLogicalOperator, VisualBitVector) -> Bool
        ofoldr1ExOptimizationIsValid (y, x) = isZeroVector bv || ofoldr1Ex op bv == (foldr1 op . otoList) bv
            where
                bv = getBitVector x
                op = getBinaryLogicalOperator y

        ofoldl1ExOptimizationIsValid :: (BinaryLogicalOperator, VisualBitVector) -> Bool
        ofoldl1ExOptimizationIsValid (y, x) = isZeroVector bv || ofoldl1Ex' op bv == (foldl1 op . otoList) bv
            where
                bv = getBitVector x
                op = getBinaryLogicalOperator y

        headExOptimizationIsValid :: VisualBitVector -> Bool
        headExOptimizationIsValid x = isZeroVector bv || headEx bv == (head . otoList) bv
            where bv = getBitVector x

        lastExOptimizationIsValid :: VisualBitVector -> Bool
        lastExOptimizationIsValid x = isZeroVector bv || lastEx bv == (last . otoList) bv
            where bv = getBitVector x

        maximumByExOptimizationIsValid :: (VisualBitVector, ComparisonOperator) -> Bool
        maximumByExOptimizationIsValid (x, y) =
            isZeroVector bv || maximumByEx op bv == (maximumBy op . otoList) bv
            where
                bv = getBitVector x
                op = getComparator y

        minimumByExOptimizationIsValid :: (VisualBitVector, ComparisonOperator) -> Bool
        minimumByExOptimizationIsValid (x, y) =
            isZeroVector bv || minimumByEx op bv == (minimumBy op . otoList) bv
            where
                bv = getBitVector x
                op = getComparator y

        oelemOptimizationIsValid :: (VisualBitVector, Bool) -> Bool
        oelemOptimizationIsValid (x, e) = oelem e bv == (oelem e . otoList) bv where bv = getBitVector x

        onotElemOptimizationIsValid :: (VisualBitVector, Bool) -> Bool
        onotElemOptimizationIsValid (x, e) = onotElem e bv == (onotElem e . otoList) bv
            where bv = getBitVector x


monoZipEquivalence :: TestTree
monoZipEquivalence = testGroup
    "Equivalence of a MonoZip"
    [ SC.testProperty "ozipWith f x === fromBits . zipWith f . (toBits x) . toBits"
        $ forAll omapOptimizationIsValid
    ]
    where
        omapOptimizationIsValid
            :: (BinaryLogicalOperator, VisualBitVectorSmall, VisualBitVectorSmall) -> Bool
        omapOptimizationIsValid (f, x, y) = ozipWith op lhs rhs
            == (fromBits . zipWith op (toBits lhs) . toBits) rhs
            where
                op  = getBinaryLogicalOperator f
                lhs = getBitVector x
                rhs = getBitVector y


{-
infixr 0 ===>
(===>) :: QC.Testable prop => Bool -> prop -> Property
False ===> _ = property True
True  ===> p = property p
-}