packages feed

hcobs-0.1.0.0: test/Spec.hs

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ConstraintKinds #-}

import Control.Monad (unless)
import Control.Monad.Identity (Identity)
import Control.Monad.Morph    (generalize, hoist)
import Data.ByteString.Lazy (ByteString, length, notElem, fromStrict)
import Data.Proxy (Proxy(..))
import Data.Reflection (Reifies, reflect, reifyNat)
import Data.Word (Word8)
import GHC.Exts (Constraint)
import GHC.TypeLits (KnownNat, CmpNat)
import Hedgehog
import Hedgehog.Gen (bytes, word8)
import Hedgehog.Range (constant, constantBounded)
import Prelude hiding (length, notElem)
import System.Exit (exitFailure)
import Unsafe.Coerce (unsafeCoerce)
import System.IO.Unsafe

import Data.Stuffed (Stuffed, stuff, unstuff, unwrap)

data Dict (c :: Constraint) where
    Dict :: (c) => Dict c

main :: IO ()
main = do
    result <- checkParallel $ Group "Stuffed properties"
                    [ ("roundtrips",
                        stuffedProperty (bytesAndExclusion 0) (const roundTrips))
                    , ("1 byte bigger every 254",
                        withTests 1000 $
                        stuffedProperty (bytesAndExclusion 1) (const biggerBy1Every254))
                    , ("Doesn't contain the excluded byte",
                        stuffedProperty (bytesAndExclusion 0) notContains) ]
    unless result exitFailure

type Generator = Gen (ByteString, Word8)

bytesAndExclusion :: Int -> Generator
bytesAndExclusion minSize = (,) <$> (fromStrict <$> bytes (constant minSize 1100)) <*> word8 constantBounded

smallerThan256 :: forall a. (KnownNat a) => Maybe (Dict (KnownNat a, CmpNat a 256 ~ 'LT))
smallerThan256 = if n < 256
        then Just $ unsafeCoerce (Dict :: Dict (KnownNat a, 'LT ~ 'LT))
        else Nothing
    where
        n = reflect (Proxy :: Proxy a)

stuffedProperty :: Generator -> (forall a. (KnownNat a, CmpNat a 256 ~ 'LT) => Word8 -> ByteString -> Proxy a -> PropertyT Identity ()) -> Property
stuffedProperty gen prop = property $ hoist generalize $ do
    (x, w) <- forAll gen
    reifyNat (fromIntegral w) $ \(p :: Proxy n) ->
        case smallerThan256 of
            Nothing -> failure
            (Just (Dict :: Dict (KnownNat n, CmpNat n 256 ~ 'LT))) -> prop w x p



roundTrips :: forall a. (KnownNat a, CmpNat a 256 ~ 'LT) => ByteString -> Proxy a -> PropertyT Identity ()
roundTrips bs _ = bs === unstuff (stuff bs :: Stuffed a)

biggerBy1Every254 :: forall a. (KnownNat a, CmpNat a 256 ~ 'LT) => ByteString -> Proxy a -> PropertyT Identity ()
biggerBy1Every254 bs _ = length stf === inputL + ((inputL `div` 255) + inputL) `div` 255 + 1
    where
        stf = unwrap (stuff bs :: Stuffed a)
        inputL = length bs


notContains :: forall a. (KnownNat a, CmpNat a 256 ~ 'LT) => Word8 -> ByteString -> Proxy a -> PropertyT Identity ()
notContains i bs _ = assert $ i `notElem` stf
    where
        stf = unwrap (stuff bs :: Stuffed a)