packages feed

xor-0.0.1.0: src-test/Main.hs

{-# LANGUAGE Haskell2010       #-}
{-# LANGUAGE OverloadedStrings #-}

-- |
-- Copyright: © 2019-2020  Herbert Valerio Riedel
-- SPDX-License-Identifier: GPL-2.0-or-later
--
module Main (main)  where

import           Control.Monad
import           Data.Bits
import           Data.Int
import           Data.Monoid             (mempty)
import           Data.Word
import qualified Foreign.Marshal         as F
import qualified Foreign.Ptr             as F
import           Text.Printf             (printf)

import qualified Data.ByteString         as BS
import qualified Data.ByteString.Lazy    as BL
import qualified Data.ByteString.Short   as SBS

import qualified Test.QuickCheck.Monadic as QCM
import           Test.Tasty
import           Test.Tasty.HUnit
import           Test.Tasty.QuickCheck   as QC

import qualified Data.XOR                as IUT

main :: IO ()
main = defaultMain tests

tests :: TestTree
tests = testGroup "Tests"
  [ testGroup "hello"
    [ testCase "REF"             $ xor32StrictByteString'ref  0x37fa213d "\x7f\x9f\x4d\x51\x58" @?= "Hello"
    , testCase "S.ByteString"    $ IUT.xor32StrictByteString' 0x37fa213d "\x7f\x9f\x4d\x51\x58" @?= (0xfa213d37,"Hello")
    , testCase "L.ByteString"    $ IUT.xor32LazyByteString    0x37fa213d "\x7f\x9f\x4d\x51\x58" @?= "Hello"
    , testCase "ShortByteString" $ IUT.xor32ShortByteString   0x37fa213d "\x7f\x9f\x4d\x51\x58" @?= "Hello"
    ]

  , testGroup "empty"
    [ testProperty "S.ByteString"    $ \msk -> IUT.xor32StrictByteString msk mempty === Data.Monoid.mempty
    , testProperty "L.ByteString"    $ \msk -> IUT.xor32LazyByteString   msk mempty === mempty
    , testProperty "ShortByteString" $ \msk -> IUT.xor32ShortByteString  msk mempty === mempty
    ]

  , testGroup "zero-xor"
    [ testProperty "S.ByteString" $ \xs -> let bs = BS.pack xs in IUT.xor32StrictByteString 0 bs === bs
    , testProperty "L.ByteString" $ \xs -> let bs = BL.fromChunks (map BS.pack xs) in IUT.xor32LazyByteString 0 bs === BL.fromChunks [BS.pack (concat xs)]
    , testProperty "ShortByteString" $ \xs -> let bs = SBS.pack xs in IUT.xor32ShortByteString 0 bs === bs
    ]

  , testProperty "xor32Ptr" $ \msk lb len8 -> QCM.monadicIO $ do
       let bufsize, ofs, len :: Int
           bufsize = 2048
           ofs     = 512 + fromIntegral (lb :: Int8)
           len     = fromIntegral (len8 :: Word8)

       QCM.run $ F.allocaBytes bufsize $ \bufptr -> do

         origbuf <- BS.packCStringLen (bufptr, bufsize)

         let (origbufPre, tmp) = BS.splitAt ofs origbuf
             (origbufMid, origbufPost) = BS.splitAt len tmp

         _ <- IUT.xor32CStringLen msk (F.castPtr bufptr `F.plusPtr` ofs,len)
         newbufIut <- BS.packCStringLen (bufptr, bufsize)

         let newbufRef = BS.concat [origbufPre, xor32StrictByteString'ref msk origbufMid, origbufPost]

         unless (BS.length newbufRef == bufsize) $ fail "internal error"

         unless (newbufRef == newbufIut) $ do
           putStrLn $ show (msk,bufsize, ofs, len)
           putStrLn $ "orig " ++ concatMap (printf "%02x") (BS.unpack origbuf)
           putStrLn $ "ref  " ++ concatMap (printf "%02x") (BS.unpack newbufRef)
           putStrLn $ "iut  " ++ concatMap (printf "%02x") (BS.unpack newbufIut)

           forM_ (zip3 [0..] (BS.unpack newbufRef) (BS.unpack newbufIut)) $ \(j,ref8,iut8) ->
             unless (ref8 == iut8) $
               printf "%d (%d): %02x %02x\n" (j::Int) (j-ofs) ref8 iut8

         return $! newbufRef == newbufIut

  , testGroup "ref-vs-iut 32-bit"
    [ testProperty "S.ByteString" $ \xs msk ->
        let bs = BS.pack xs
        in IUT.xor32StrictByteString msk bs === xor32StrictByteString'ref msk bs
    , testProperty "L.ByteString" $ \xs msk ->
        let bs  = BL.fromChunks (map BS.pack xs)
            bs' = BS.pack (concat xs)
        in IUT.xor32LazyByteString msk bs === BL.fromStrict (xor32StrictByteString'ref msk bs')
    , testProperty "ShortByteString" $ \xs msk ->
        let bs = SBS.pack xs
        in IUT.xor32ShortByteString msk bs === xor32ShortByteString'ref msk bs
    ]

  , testGroup "ref-vs-iut 8-bit"
    [ testProperty "S.ByteString" $ \xs msk ->
        let bs = BS.pack xs
        in IUT.xor8StrictByteString msk bs === xor8StrictByteString'ref msk bs
    , testProperty "L.ByteString" $ \xs msk ->
        let bs  = BL.fromChunks (map BS.pack xs)
        in IUT.xor8LazyByteString msk bs === xor8LazyByteString'ref msk bs
    , testProperty "ShortByteString" $ \xs msk ->
        let bs = SBS.pack xs
        in IUT.xor8ShortByteString msk bs === xor8ShortByteString'ref msk bs
    ]

  , testGroup "self-inverse"
    [ testProperty "S.ByteString" $ \xs msk ->
        let bs = BS.pack xs
        in IUT.xor32StrictByteString msk (IUT.xor32StrictByteString msk bs) === bs
    , testProperty "L.ByteString" $ \xs msk ->
        let bs  = BL.fromChunks (map BS.pack xs)
            bs' = BL.fromChunks [BS.pack (concat xs)]
        in IUT.xor32LazyByteString msk (IUT.xor32LazyByteString msk bs) === bs'
    , testProperty "ShortByteString" $ \xs msk ->
        let bs = SBS.pack xs
        in IUT.xor32ShortByteString msk (IUT.xor32ShortByteString msk bs) === bs
    ]
  ]

xor32StrictByteString'ref :: Word32 -> BS.ByteString -> BS.ByteString
xor32StrictByteString'ref 0    = id
xor32StrictByteString'ref msk0 = snd . BS.mapAccumL go msk0
  where
    go :: Word32 -> Word8 -> (Word32,Word8)
    go msk b = let b'   = fromIntegral msk' `xor` b
                   msk' = rotateL msk 8
               in b' `seq` (msk',b')

xor8StrictByteString'ref :: Word8 -> BS.ByteString -> BS.ByteString
xor8StrictByteString'ref 0    = id
xor8StrictByteString'ref msk0 = BS.map (xor msk0)

liftSBS :: (t -> BS.ByteString -> BS.ByteString) -> t -> SBS.ShortByteString -> SBS.ShortByteString
liftSBS op = \msk -> SBS.toShort . op msk . SBS.fromShort

liftBL :: (t -> BS.ByteString -> BS.ByteString) -> t -> BL.ByteString -> BL.ByteString
liftBL op = \msk -> BL.fromStrict . op msk . BL.toStrict

xor32ShortByteString'ref :: Word32 -> SBS.ShortByteString -> SBS.ShortByteString
xor32ShortByteString'ref = liftSBS xor32StrictByteString'ref

xor8ShortByteString'ref :: Word8 -> SBS.ShortByteString -> SBS.ShortByteString
xor8ShortByteString'ref = liftSBS xor8StrictByteString'ref

xor8LazyByteString'ref :: Word8 -> BL.ByteString -> BL.ByteString
xor8LazyByteString'ref = liftBL xor8StrictByteString'ref