{-# LANGUAGE CPP, ScopedTypeVariables #-}
module Data.ByteString.Builder.Prim.TestUtils (
evalF
, evalB
, showF
, showB
, testF
, testBoundedF
, testFixedBoundF
, compareImpls
, testBoundedB
, charUtf8_list
, char8_list
, encodeASCII
, encodeForcedASCII
, char7_list
, dec_list
, hex_list
, wordHexFixed_list
, int8HexFixed_list
, int16HexFixed_list
, int32HexFixed_list
, int64HexFixed_list
, floatHexFixed_list
, doubleHexFixed_list
, parseVar
, bigEndian_list
, littleEndian_list
, hostEndian_list
, float_list
, double_list
, coerceFloatToWord32
, coerceDoubleToWord64
) where
import Control.Arrow (first)
import Data.ByteString.Builder.Prim
import qualified Data.ByteString as S
import qualified Data.ByteString.Internal as S
import qualified Data.ByteString.Builder.Prim.Internal as I
import Data.Char (chr, ord)
import Numeric (showHex)
#if MIN_VERSION_base(4,4,0)
import Foreign hiding (unsafePerformIO)
import System.IO.Unsafe (unsafePerformIO)
#else
import Foreign
#endif
import System.ByteOrder
#if defined(HAVE_TEST_FRAMEWORK)
import Test.HUnit (assertBool)
import Test.Framework
import Test.Framework.Providers.HUnit
import Test.Framework.Providers.QuickCheck2
#else
import TestFramework
#endif
import Test.QuickCheck (Arbitrary(..))
testBoundedProperty :: forall a. (Arbitrary a, Show a, Bounded a)
=> String -> (a -> Bool) -> Test
testBoundedProperty name p = testGroup name
[ testProperty name p
, testCase (name ++ " minBound") $ assertBool "minBound" (p (minBound :: a))
, testCase (name ++ " maxBound") $ assertBool "minBound" (p (maxBound :: a))
]
quote :: String -> String
quote cs = '`' : cs ++ "'"
quoteWord8s :: [Word8] -> String
quoteWord8s = quote . map (chr . fromIntegral)
evalF :: FixedPrim a -> a -> [Word8]
evalF fe = S.unpack . S.unsafeCreate (I.size fe) . I.runF fe
evalB :: BoundedPrim a -> a -> [Word8]
evalB be x = S.unpack $ unsafePerformIO $
S.createAndTrim (I.sizeBound be) $ \op -> do
op' <- I.runB be x op
return (op' `minusPtr` op)
showF :: FixedPrim a -> a -> String
showF fe = map (chr . fromIntegral) . evalF fe
showB :: BoundedPrim a -> a -> String
showB be = map (chr . fromIntegral) . evalB be
testF :: (Arbitrary a, Show a)
=> String
-> (a -> [Word8])
-> FixedPrim a
-> Test
testF name ref fe =
testProperty name prop
where
prop x
| y == y' = True
| otherwise = error $ unlines $
[ "testF: results disagree for " ++ quote (show x)
, " fixed encoding: " ++ show y ++ " " ++ quoteWord8s y
, " reference: " ++ show y'++ " " ++ quoteWord8s y'
]
where
y = evalF fe x
y' = ref x
testBoundedF :: (Arbitrary a, Bounded a, Show a)
=> String
-> (a -> [Word8])
-> FixedPrim a
-> Test
testBoundedF name ref fe =
testBoundedProperty name $ \x -> evalF fe x == ref x
testFixedBoundF :: (Arbitrary a, Show a, Integral a)
=> String
-> (a -> a -> [Word8])
-> (a -> FixedPrim a)
-> Test
testFixedBoundF name ref bfe =
testProperty name prop
where
prop (b, x0)
| y == y' = True
| otherwise = error $ unlines $
[ "testF: results disagree for " ++ quote (show (b, x))
, " fixed encoding: " ++ show y ++ " " ++ quoteWord8s y
, " reference: " ++ show y'++ " " ++ quoteWord8s y'
]
where
x | b == 0 = 0
| otherwise = x0 `mod` b
y = evalF (bfe b) x
y' = ref b x
testBoundedB :: (Arbitrary a, Bounded a, Show a)
=> String
-> (a -> [Word8])
-> BoundedPrim a
-> Test
testBoundedB name ref fe =
testBoundedProperty name check
where
check x
| y == y' = True
| otherwise = error $ unlines $
[ "testBoundedB: results disagree for " ++ quote (show x)
, " fixed encoding: " ++ show y ++ " " ++ quoteWord8s y
, " reference: " ++ show y'++ " " ++ quoteWord8s y'
]
where
y = evalB fe x
y' = ref x
compareImpls :: (Arbitrary a, Show a, Show b, Eq b)
=> TestName -> (a -> b) -> (a -> b) -> Test
compareImpls name f1 f2 =
testProperty name check
where
check x
| y1 == y2 = True
| otherwise = error $ unlines $
[ "compareImpls: results disagree for " ++ quote (show x)
, " f1: " ++ show y1
, " f2: " ++ show y2
]
where
y1 = f1 x
y2 = f2 x
char8_list :: Char -> [Word8]
char8_list = return . fromIntegral . ord
charUtf8_list :: Char -> [Word8]
charUtf8_list =
map fromIntegral . encode . ord
where
encode oc
| oc <= 0x7f = [oc]
| oc <= 0x7ff = [ 0xc0 + (oc `shiftR` 6)
, 0x80 + oc .&. 0x3f
]
| oc <= 0xffff = [ 0xe0 + (oc `shiftR` 12)
, 0x80 + ((oc `shiftR` 6) .&. 0x3f)
, 0x80 + oc .&. 0x3f
]
| otherwise = [ 0xf0 + (oc `shiftR` 18)
, 0x80 + ((oc `shiftR` 12) .&. 0x3f)
, 0x80 + ((oc `shiftR` 6) .&. 0x3f)
, 0x80 + oc .&. 0x3f
]
encodeASCII :: String -> [Word8]
encodeASCII =
map encode
where
encode c
| c < '\x7f' = fromIntegral $ ord c
| otherwise = error $ "encodeASCII: non-ASCII character '" ++ [c] ++ "'"
encodeForcedASCII :: String -> [Word8]
encodeForcedASCII = map ((.&. 0x7f) . fromIntegral . ord)
char7_list :: Char -> [Word8]
char7_list = encodeForcedASCII . return
dec_list :: Show a => a -> [Word8]
dec_list = encodeASCII . show
hex_list :: (Integral a, Show a) => a -> [Word8]
hex_list = encodeASCII . (\x -> showHex x "")
wordHexFixed_list :: (Storable a, Integral a, Show a) => a -> [Word8]
wordHexFixed_list x =
encodeASCII $ pad (2 * sizeOf x) $ showHex x ""
where
pad n cs = replicate (n - length cs) '0' ++ cs
int8HexFixed_list :: Int8 -> [Word8]
int8HexFixed_list = wordHexFixed_list . (fromIntegral :: Int8 -> Word8 )
int16HexFixed_list :: Int16 -> [Word8]
int16HexFixed_list = wordHexFixed_list . (fromIntegral :: Int16 -> Word16)
int32HexFixed_list :: Int32 -> [Word8]
int32HexFixed_list = wordHexFixed_list . (fromIntegral :: Int32 -> Word32)
int64HexFixed_list :: Int64 -> [Word8]
int64HexFixed_list = wordHexFixed_list . (fromIntegral :: Int64 -> Word64)
floatHexFixed_list :: Float -> [Word8]
floatHexFixed_list = float_list wordHexFixed_list
doubleHexFixed_list :: Double -> [Word8]
doubleHexFixed_list = double_list wordHexFixed_list
bigEndian_list :: (Storable a, Bits a, Integral a) => a -> [Word8]
bigEndian_list = reverse . littleEndian_list
littleEndian_list :: (Storable a, Bits a, Integral a) => a -> [Word8]
littleEndian_list x =
map (fromIntegral . (x `shiftR`) . (8*)) $ [0..sizeOf x - 1]
hostEndian_list :: (Storable a, Bits a, Integral a) => a -> [Word8]
hostEndian_list = case byteOrder of
LittleEndian -> littleEndian_list
BigEndian -> bigEndian_list
_ -> error $
"bounded-encoding: unsupported byteorder '" ++ show byteOrder ++ "'"
float_list :: (Word32 -> [Word8]) -> Float -> [Word8]
float_list f = f . coerceFloatToWord32
double_list :: (Word64 -> [Word8]) -> Double -> [Word8]
double_list f = f . coerceDoubleToWord64
{-# NOINLINE coerceFloatToWord32 #-}
coerceFloatToWord32 :: Float -> Word32
coerceFloatToWord32 x = unsafePerformIO (with x (peek . castPtr))
{-# NOINLINE coerceDoubleToWord64 #-}
coerceDoubleToWord64 :: Double -> Word64
coerceDoubleToWord64 x = unsafePerformIO (with x (peek . castPtr))
parseVar :: (Num a, Bits a) => [Word8] -> (a, [Word8])
parseVar =
go
where
go [] = error "parseVar: unterminated variable length int"
go (w:ws)
| w .&. 0x80 == 0 = (fromIntegral w, ws)
| otherwise = first add (go ws)
where
add x = (x `shiftL` 7) .|. (fromIntegral w .&. 0x7f)