packages feed

bytestring-0.10.0.1: tests/builder/Data/ByteString/Lazy/Builder/BasicEncoding/Tests.hs

{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- Copyright   : (c) 2011 Simon Meier
-- License     : BSD3-style (see LICENSE)
--
-- Maintainer  : Simon Meier <iridcode@gmail.com>
-- Stability   : experimental
-- Portability : tested on GHC only
--
-- Testing all encodings provided by this library.

module Data.ByteString.Lazy.Builder.BasicEncoding.Tests (tests) where

import           Control.Arrow (first)

import           Data.Char  (ord)
import qualified Data.ByteString.Lazy                                 as L
import           Data.ByteString.Lazy.Builder
import qualified Data.ByteString.Lazy.Builder.BasicEncoding           as BE
import qualified Data.ByteString.Lazy.Builder.BasicEncoding.Extras    as BE
import           Data.ByteString.Lazy.Builder.BasicEncoding.TestUtils

import           Numeric (showHex)

import           Foreign

import           TestFramework
import           Test.QuickCheck (Arbitrary)


tests :: [Test]
tests = concat [ testsBinary, testsASCII, testsChar8, testsUtf8
               , testsCombinatorsB ]


------------------------------------------------------------------------------
-- Binary
------------------------------------------------------------------------------

testsBinary :: [Test]
testsBinary =
  [ testBoundedF "word8"     bigEndian_list    BE.word8
  , testBoundedF "int8"      bigEndian_list    BE.int8

  --  big-endian
  , testBoundedF "int16BE"   bigEndian_list    BE.int16BE
  , testBoundedF "int32BE"   bigEndian_list    BE.int32BE
  , testBoundedF "int64BE"   bigEndian_list    BE.int64BE

  , testBoundedF "word16BE"  bigEndian_list    BE.word16BE
  , testBoundedF "word32BE"  bigEndian_list    BE.word32BE
  , testBoundedF "word64BE"  bigEndian_list    BE.word64BE

  , testF "floatLE"     (float_list  littleEndian_list) BE.floatLE
  , testF "doubleLE"    (double_list littleEndian_list) BE.doubleLE

  --  little-endian
  , testBoundedF "int16LE"   littleEndian_list BE.int16LE
  , testBoundedF "int32LE"   littleEndian_list BE.int32LE
  , testBoundedF "int64LE"   littleEndian_list BE.int64LE

  , testBoundedF "word16LE"  littleEndian_list BE.word16LE
  , testBoundedF "word32LE"  littleEndian_list BE.word32LE
  , testBoundedF "word64LE"  littleEndian_list BE.word64LE

  , testF "floatBE"     (float_list  bigEndian_list)   BE.floatBE
  , testF "doubleBE"    (double_list bigEndian_list)   BE.doubleBE

  --  host dependent
  , testBoundedF "int16Host"   hostEndian_list  BE.int16Host
  , testBoundedF "int32Host"   hostEndian_list  BE.int32Host
  , testBoundedF "int64Host"   hostEndian_list  BE.int64Host
  , testBoundedF "intHost"     hostEndian_list  BE.intHost

  , testBoundedF "word16Host"  hostEndian_list  BE.word16Host
  , testBoundedF "word32Host"  hostEndian_list  BE.word32Host
  , testBoundedF "word64Host"  hostEndian_list  BE.word64Host
  , testBoundedF "wordHost"    hostEndian_list  BE.wordHost

  , testF "floatHost"   (float_list  hostEndian_list)   BE.floatHost
  , testF "doubleHost"  (double_list hostEndian_list)   BE.doubleHost

  , testBoundedB "word8Var"     genVar_list  BE.word8Var
  , testBoundedB "word16Var"    genVar_list  BE.word16Var
  , testBoundedB "word32Var"    genVar_list  BE.word32Var
  , testBoundedB "word64Var"    genVar_list  BE.word64Var
  , testBoundedB "wordVar"      genVar_list  BE.wordVar

  , testBoundedB "int8Var"     int8Var_list   BE.int8Var
  , testBoundedB "int16Var"    int16Var_list  BE.int16Var
  , testBoundedB "int32Var"    int32Var_list  BE.int32Var
  , testBoundedB "int64Var"    int64Var_list  BE.int64Var
  , testBoundedB "intVar"      intVar_list    BE.intVar

  , testBoundedB "int8VarSigned"     (int8Var_list  . zigZag)  BE.int8VarSigned
  , testBoundedB "int16VarSigned"    (int16Var_list . zigZag)  BE.int16VarSigned
  , testBoundedB "int32VarSigned"    (int32Var_list . zigZag)  BE.int32VarSigned
  , testBoundedB "int64VarSigned"    (int64Var_list . zigZag)  BE.int64VarSigned
  , testBoundedB "intVarSigned"      (intVar_list   . zigZag)  BE.intVarSigned

  , testGroup "parseable"
    [ prop_zigZag_parseable  "int8VarSigned"   unZigZagInt8  BE.int8VarSigned
    , prop_zigZag_parseable  "int16VarSigned"  unZigZagInt16 BE.int16VarSigned
    , prop_zigZag_parseable  "int32VarSigned"  unZigZagInt32 BE.int32VarSigned
    , prop_zigZag_parseable  "int64VarSigned"  unZigZagInt64 BE.int64VarSigned
    , prop_zigZag_parseable  "intVarSigned"    unZigZagInt   BE.intVarSigned
    ]

  , testFixedBoundF "wordVarFixedBound"   wordVarFixedBound_list    BE.wordVarFixedBound
  , testFixedBoundF "word64VarFixedBound" word64VarFixedBound_list  BE.word64VarFixedBound

  ]


-- Variable length encodings
----------------------------

-- | Variable length encoding.
genVar_list :: (Ord a, Num a, Bits a, Integral a) => a -> [Word8]
genVar_list x
  | x <= 0x7f = sevenBits            : []
  | otherwise = (sevenBits .|. 0x80) : genVar_list (x `shiftR` 7)
  where
    sevenBits = fromIntegral x .&. 0x7f

int8Var_list :: Int8 -> [Word8]
int8Var_list  = genVar_list . (fromIntegral :: Int8 -> Word8)

int16Var_list :: Int16 -> [Word8]
int16Var_list = genVar_list . (fromIntegral :: Int16 -> Word16)

int32Var_list :: Int32 -> [Word8]
int32Var_list = genVar_list . (fromIntegral :: Int32 -> Word32)

int64Var_list :: Int64 -> [Word8]
int64Var_list = genVar_list . (fromIntegral :: Int64 -> Word64)

intVar_list :: Int -> [Word8]
intVar_list = genVar_list . (fromIntegral :: Int -> Word)


-- | The so-called \"zig-zag\" encoding from Google's protocol buffers.
-- It maps integers of small magnitude to naturals of small
-- magnitude by encoding negative integers as odd naturals and positive
-- integers as even naturals.
--
-- For example: @0 -> 0,  -1 -> 1, 1 -> 2, -2 -> 3, 2 -> 4, ...@
--
-- PRE: 'a' must be a signed integer type.
zigZag :: (Storable a, Bits a) => a -> a
zigZag x = (x `shiftL` 1) `xor` (x `shiftR` (8 * sizeOf x - 1))


-- | Reversing the zigZag encoding.
--
-- PRE: 'a' must be an unsigned integer type.
--
-- forall x. fromIntegral x ==
--           unZigZag ((fromIntegral :: IntX -> WordX) (zigZag x))
--
unZigZag :: (Storable a, Num a, Bits a) => a -> a
unZigZag x = (x `shiftR` 1) `xor` negate (x .&. 1)

unZigZagInt8 :: Int8 -> Int8
unZigZagInt8 = (fromIntegral :: Word8 -> Int8) . unZigZag . fromIntegral

unZigZagInt16 :: Int16 -> Int16
unZigZagInt16 = (fromIntegral :: Word16 -> Int16) . unZigZag . fromIntegral

unZigZagInt32 :: Int32 -> Int32
unZigZagInt32 = (fromIntegral :: Word32 -> Int32) . unZigZag . fromIntegral

unZigZagInt64 :: Int64 -> Int64
unZigZagInt64 = (fromIntegral :: Word64 -> Int64) . unZigZag . fromIntegral

unZigZagInt :: Int -> Int
unZigZagInt = (fromIntegral :: Word -> Int) . unZigZag . fromIntegral

-- | Check that the 'intVarSigned' encodings are parseable.
prop_zigZag_parseable :: (Arbitrary t, Num b, Bits b, Show t, Eq t)
    => String -> (b -> t) -> BE.BoundedEncoding t -> Test
prop_zigZag_parseable name unZig be =
  compareImpls name (\x -> (x, [])) (first unZig . parseVar . BE.evalB be)

-- | Variable length encoding to a fixed number of bytes (pad / truncate).
genVarFixedBound_list :: (Ord a, Num a, Bits a, Integral a)
                 => Int
                 -> a -> [Word8]
genVarFixedBound_list n x
  | n <= 1    = sevenBits            : []
  | otherwise = (sevenBits .|. 0x80) : genVarFixedBound_list (n - 1) (x `shiftR` 7)
  where
    sevenBits = fromIntegral x .&. 0x7f

wordVarFixedBound_list :: Word -> Word -> [Word8]
wordVarFixedBound_list bound = genVarFixedBound_list (length $ genVar_list bound)

word64VarFixedBound_list :: Word64 -> Word64 -> [Word8]
word64VarFixedBound_list bound = genVarFixedBound_list (length $ genVar_list bound)

-- Somehow this function doesn't really make sense, as the bound must be
-- greater when interpreted as an unsigned integer.
--
-- intVarFixedBound_list :: Int -> Int -> [Word8]
-- intVarFixedBound_list bound = wordVarFixedBound_list (fromIntegral bound) . fromIntegral


------------------------------------------------------------------------------
-- Latin-1  aka  Char8
------------------------------------------------------------------------------

testsChar8 :: [Test]
testsChar8 =
  [ testBoundedF "char8"     char8_list        BE.char8  ]


------------------------------------------------------------------------------
-- ASCII
------------------------------------------------------------------------------

testsASCII :: [Test]
testsASCII =
  [ testBoundedF "char7" char7_list BE.char7

  , testBoundedB "int8Dec"   dec_list BE.int8Dec
  , testBoundedB "int16Dec"  dec_list BE.int16Dec
  , testBoundedB "int32Dec"  dec_list BE.int32Dec
  , testBoundedB "int64Dec"  dec_list BE.int64Dec
  , testBoundedB "intDec"    dec_list BE.intDec

  , testBoundedB "word8Dec"  dec_list BE.word8Dec
  , testBoundedB "word16Dec" dec_list BE.word16Dec
  , testBoundedB "word32Dec" dec_list BE.word32Dec
  , testBoundedB "word64Dec" dec_list BE.word64Dec
  , testBoundedB "wordDec"   dec_list BE.wordDec

  , testBoundedB "word8Hex"  hex_list BE.word8Hex
  , testBoundedB "word16Hex" hex_list BE.word16Hex
  , testBoundedB "word32Hex" hex_list BE.word32Hex
  , testBoundedB "word64Hex" hex_list BE.word64Hex
  , testBoundedB "wordHex"   hex_list BE.wordHex

  , testBoundedF "word8HexFixed"  wordHexFixed_list BE.word8HexFixed
  , testBoundedF "word16HexFixed" wordHexFixed_list BE.word16HexFixed
  , testBoundedF "word32HexFixed" wordHexFixed_list BE.word32HexFixed
  , testBoundedF "word64HexFixed" wordHexFixed_list BE.word64HexFixed

  , testBoundedF "int8HexFixed"  int8HexFixed_list  BE.int8HexFixed
  , testBoundedF "int16HexFixed" int16HexFixed_list BE.int16HexFixed
  , testBoundedF "int32HexFixed" int32HexFixed_list BE.int32HexFixed
  , testBoundedF "int64HexFixed" int64HexFixed_list BE.int64HexFixed

  , testF "floatHexFixed"  floatHexFixed_list  BE.floatHexFixed
  , testF "doubleHexFixed" doubleHexFixed_list BE.doubleHexFixed

  , testFixedBoundF "wordDecFixedBound"
      (genDecFixedBound_list 'x') (BE.wordDecFixedBound 'x')

  , testFixedBoundF "word64DecFixedBound"
      (genDecFixedBound_list 'x') (BE.word64DecFixedBound 'x')

  , testFixedBoundF "wordHexFixedBound"
      (genHexFixedBound_list 'x') (BE.wordHexFixedBound 'x')

  , testFixedBoundF "word64HexFixedBound"
      (genHexFixedBound_list 'x') (BE.word64HexFixedBound 'x')
  ]

-- | PRE: positive bound and value.
genDecFixedBound_list :: (Show a, Integral a)
                      => Char    -- ^ Padding character.
                      -> a       -- ^ Max value to be encoded.
                      -> a       -- ^ Value to encode.
                      -> [Word8]
genDecFixedBound_list padChar bound =
    encodeASCII . pad . show
  where
    n      = length $ show bound
    pad cs = replicate (n - length cs) padChar ++ cs

-- | PRE: positive bound and value.
genHexFixedBound_list :: (Show a, Integral a)
                      => Char    -- ^ Padding character.
                      -> a       -- ^ Max value to be encoded.
                      -> a       -- ^ Value to encode.
                      -> [Word8]
genHexFixedBound_list padChar bound =
    encodeASCII . pad . (`showHex` "")
  where
    n      = length $ (`showHex` "") bound
    pad cs = replicate (n - length cs) padChar ++ cs


------------------------------------------------------------------------------
-- UTF-8
------------------------------------------------------------------------------

testsUtf8 :: [Test]
testsUtf8 =
  [ testBoundedB "charUtf8"  charUtf8_list  BE.charUtf8 ]


------------------------------------------------------------------------------
-- BoundedEncoding combinators
------------------------------------------------------------------------------

maybeB :: BE.BoundedEncoding () -> BE.BoundedEncoding a -> BE.BoundedEncoding (Maybe a)
maybeB nothing just = maybe (Left ()) Right BE.>$< BE.eitherB nothing just

testsCombinatorsB :: [Test]
testsCombinatorsB =
  [ compareImpls "mapMaybe (via BoundedEncoding)"
        (L.pack . concatMap encChar)
        (toLazyByteString . encViaBuilder)

  , compareImpls "filter (via BoundedEncoding)"
        (L.pack . filter (< 32))
        (toLazyByteString . BE.encodeListWithB (BE.ifB (< 32) (BE.fromF BE.word8) BE.emptyB))

  , compareImpls "pairB"
        (L.pack . concatMap (\(c,w) -> charUtf8_list c ++ [w]))
        (toLazyByteString . BE.encodeListWithB
            ((\(c,w) -> (c,(w,undefined))) BE.>$<
                BE.charUtf8 BE.>*< (BE.fromF BE.word8) BE.>*< (BE.fromF BE.emptyF)))
  ]
  where
    encChar = maybe [112] (hostEndian_list . ord)

    encViaBuilder = BE.encodeListWithB $ maybeB (BE.fromF $ (\_ -> 112) BE.>$< BE.word8)
                                                (ord BE.>$< (BE.fromF $ BE.intHost))