packages feed

serokell-util-0.9.0: test/Test/Serokell/Util/CommonSpec.hs

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ViewPatterns              #-}

module Test.Serokell.Util.CommonSpec
       ( spec
       ) where

import Universum hiding (show, toList)

import Data.Foldable (toList)
import Data.List (genericIndex, intersect)
import Prelude (show)
import Test.Hspec (Spec, describe)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (Arbitrary (..), Gen, NonEmptyList (..), oneof)
import Test.QuickCheck.Instances ()

import qualified Serokell.Util.Common as C

spec :: Spec
spec =
    describe "Common" $ do
        describe "enumerate" $
            prop description_enumerateCheck enumerateCheckIndexes
        describe "indexModulo" $
            prop description_indexModulo indexModuloCorrectIndex
        describe "indexedSubList" $
            prop description_indexedSubListNeg indexedSublistWhenNegative
  where
    description_enumerateCheck =
      "enumerates a structure in sequence, " ++
      "starting from index 0"
    description_indexModulo =
      "returns the element of the the list with " ++
      "given index modulo length of the list"
    description_indexedSubListNeg =
      "negative indices, given that the lower " ++
      "bound is lesser than the upper bound, result in valid indexation"

-- TODO: consider using `SomeValue` to generate different types of
-- values.  I don't know how to add more than one quantifier to type
-- definition. Also consider using GADTs to achieve it.
type Value = Int

data SomeTraversable =
    forall t. (Container (t Value), Traversable t, Show (t Value), Arbitrary (t Value)) =>
              SomeTraversable (t Value)

instance Show SomeTraversable where
    show (SomeTraversable t) = show t

instance Arbitrary SomeTraversable where
    arbitrary =
        oneof
            [ SomeTraversable <$> (arbitrary :: Gen [Value])
            , SomeTraversable <$> (arbitrary :: Gen (Vector Value))
            ]

enumerateCheckIndexes :: SomeTraversable -> Bool
enumerateCheckIndexes (SomeTraversable values) =
    let indexedVals = C.enumerate values
        indexList   = toList $ fmap fst indexedVals
        indexNum    = genericLength indexList
    in null values || indexList == [0 :: Word .. indexNum-1]

indexModuloCorrectIndex
    :: NonEmptyList Int -> Int -> Bool
indexModuloCorrectIndex (getNonEmpty -> lst) ind =
    let len = genericLength lst
        atModuloIndex = lst `genericIndex` (ind `mod` len)
    in atModuloIndex == C.indexModulo lst ind

indexedSublistWhenNegative
    :: (Int, Int) -> [Int] -> Bool
indexedSublistWhenNegative (lo, hi) lst
    | hi <= lo = indexList lo hi == []
    | hi <= 0 = indexList lo hi == []
    | len -1 < lo = indexList lo hi == []
    | otherwise = testIndexes lo hi == indexList lo hi
  where
    len = length lst
    indexList l h = map fst $ C.indexedSubList (l, h) lst
    testIndexes l h = intersect [l .. h - 1] [0 .. len - 1]