packages feed

snappy-c-0.1.0: test/Test/Prop/RoundTrip.hs

module Test.Prop.RoundTrip
  ( tests
  ) where

import Test.Prop.Orphans ()

import Codec.Compression.SnappyC.Raw qualified as Raw
import Codec.Compression.SnappyC.Framed

import Control.Monad
import Data.ByteString qualified as Strict (ByteString)
import Data.ByteString qualified as BS.Strict
import Data.ByteString.Lazy qualified as BS.Lazy
import Test.Tasty
import Test.Tasty.QuickCheck

tests :: TestTree
tests =
    localOption (QuickCheckTests 1000) $
      testGroup "Test.Prop.RoundTrip"
        [  testProperty "rawRoundTripId" $
              sizeTable prop_rawRoundTripId
        ,  testProperty "framedRoundTripId" $
              sizeTable prop_framedRoundTripId
        ,  testProperty "framedChunkSizeRoundTripId" $
              chunkSizeTable prop_framedChunkSizeRoundTripId
        ,  testProperty "framedSlicedRoundTripId" $
              prop_framedSlicedRoundTripId
        ]

-- | Roundtripping raw compression and decompression is identity
prop_rawRoundTripId :: Strict.ByteString -> Bool
prop_rawRoundTripId src =
    (Just src ==) $ Raw.decompress (Raw.compress src)

-- | Roundtripping framed compression and decompression is identity
prop_framedRoundTripId :: Strict.ByteString -> Bool
prop_framedRoundTripId src =
    src == roundTripped
  where
    roundTripped =
          BS.Lazy.toStrict
        . decompress
        . compress
        $ BS.Lazy.fromStrict src

-- | The @decompress . compress@ identity holds for arbitrary compression chunk
-- sizes.
prop_framedChunkSizeRoundTripId :: EncodeParams -> Strict.ByteString -> Bool
prop_framedChunkSizeRoundTripId eps src =
    let
      (compressedChunks, e') = compressStep eps initialEncoder src
      compressedChunksRest   = finalizeEncoder eps e'

      roundTripped =
          BS.Lazy.toStrict
        . decompress
        . BS.Lazy.fromChunks
        $ streamId : compressedChunks <> compressedChunksRest
    in
      src == roundTripped
  where
    (streamId, initialEncoder) = initializeEncoder

-- | The @decompress . compress@ identity holds for arbitrary slicing of
-- compression and decompression input.
prop_framedSlicedRoundTripId ::
     EncodeParams
  -> Slices
  -- ^ Compression input slices
  -> Slices
  -- ^ Decompression input slices
  -> Strict.ByteString
  -> Bool
prop_framedSlicedRoundTripId eps compressSlices decompressSlices src =
    let
      compressed =
          BS.Lazy.toStrict
        . compressWithParams eps
        . BS.Lazy.fromChunks
        $ slice compressSlices src

      roundTripped =
          BS.Lazy.toStrict
        . decompress
        . BS.Lazy.fromChunks
        $ slice decompressSlices compressed
    in
      src == roundTripped


-------------------------------------------------------------------------------
-- Auxiliary
-------------------------------------------------------------------------------

data Slices = Slices [Int]
  deriving Show

-- | Slices the input into chunks the size of each of the given 'Slices'
slice :: Slices -> Strict.ByteString -> [Strict.ByteString]
slice (Slices []) bs     = [bs]
slice _           ""     = []
slice (Slices (s:ss)) bs =
    let (sliced, rest) = BS.Strict.splitAt s bs
    in  sliced : slice (Slices ss) rest

instance Arbitrary Slices where
  arbitrary = do
      slices <- replicateM 100
        $ frequency
            [ (9, choose (0   , 1000))
            , (1, choose (1000, 1_000_000))
            ]
      return $ Slices slices

  shrink (Slices slices) = [ Slices slices' | slices' <- shrink slices ]

sizeTable ::
     Testable prop
  => (Strict.ByteString -> prop)
  -> Strict.ByteString
  -> Property
sizeTable f bs =
    tabulate "size"
      [ "10^" ++ show (sizeBracket (BS.Strict.length bs))
      ]
      (f bs)
  where
    sizeBracket :: Int -> Int
    sizeBracket 0 = 1
    sizeBracket x = floor @Double . logBase 10 $ fromIntegral x

chunkSizeTable ::
     Testable prop
  => (EncodeParams -> prop)
  -> EncodeParams
  -> Property
chunkSizeTable f inp@(EncodeParams fs _) =
    tabulate "frame size"
      [ "10^" ++ show (sizeBracket (unFrameSize fs))
      ]
      (f inp)
  where
    sizeBracket :: Int -> Int
    sizeBracket 0 = 1
    sizeBracket x = floor @Double . logBase 10 $ fromIntegral x