ptr-0.16.8.2: tests/Main.hs
module Main where
import qualified Data.ByteString as D
import qualified Data.ByteString.Char8 as I
import qualified Data.Serialize as J
import qualified Data.Vector.Unboxed as K
import qualified Ptr.ByteString as A
import qualified Ptr.Parse as G
import qualified Ptr.Peek as C
import qualified Ptr.Poke as B
import qualified Ptr.PokeAndPeek as E
import qualified Ptr.Poking as F
import qualified Ptr.Read as H
import Test.QuickCheck
import Test.QuickCheck.Instances
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Test.Tasty.Runners
import Prelude hiding (choose)
main =
defaultMain $
testGroup
"All tests"
[ testProperty "ASCII Numbers ByteString Roundtrip" $ \(numbers :: [Word64]) ->
let expected = foldMap (fromString . show) numbers
actual = A.poking (foldMap F.asciiIntegral numbers)
in expected === actual,
testProperty "Poke and peek (bytes)" $ \input -> input === fromJust (pokeThenPeek (B.bytes (D.length input)) (C.bytes (D.length input))) input,
testProperty "Poke and peek (word8)" $ \input -> input === fromJust (pokeThenPeek B.word8 C.word8) input,
testProperty "Poke and peek (beWord32)" $ \input -> input === fromJust (pokeThenPeek B.beWord32 C.beWord32) input,
testProperty "Poke and peek (beWord64)" $ \input -> input === fromJust (pokeThenPeek B.beWord64 C.beWord64) input,
testProperty "PokeAndPeek composition" $ \input -> input === pokeAndPeek ((,) <$> lmap fst E.word8 <*> lmap snd E.beWord32) input,
testGroup
"Poking"
[ testCase "asciiPaddedAndTrimmedIntegral" $ do
assertEqual "" "001" (A.poking (F.asciiPaddedAndTrimmedIntegral 3 1))
assertEqual "" "001" (A.poking (F.asciiPaddedAndTrimmedIntegral 3 2001))
assertEqual "" "000" (A.poking (F.asciiPaddedAndTrimmedIntegral 3 (-1))),
testCase "asciiUtcTimeInIso8601" $ do
assertEqual "" "2017-02-01T05:03:58Z" (A.poking (F.asciiUtcTimeInIso8601 (read "2017-02-01 05:03:58Z"))),
testCase "fromString" $ do
assertEqual "" "123" (A.poking "123"),
testCase "intercalateVector" $ do
assertEqual "" "1,2,3,4" (A.poking (F.intercalateVector F.asciiIntegral "," (K.fromList [1 :: Word8, 2, 3, 4])))
],
parsing,
testGroup
"Regression"
[ testCase "https://github.com/nikita-volkov/hasql-dynamic-statements/issues/2" $
assertEqual "" "$1000" (A.poking (F.word8 36 <> F.asciiIntegral 1000))
],
testGroup "Read" $
let consumeManyByteStrings :: H.Read a -> [ByteString] -> Maybe a
consumeManyByteStrings read = \case
head : tail ->
H.runOnByteString read head & \case
Left newRead -> consumeManyByteStrings newRead tail
Right (res, rem) -> Just res
_ ->
Nothing
againstByteString :: (Eq a, Show a) => H.Read a -> (ByteString -> a) -> [ByteString] -> Property
againstByteString read fromByteString chunks =
consumeManyByteStrings read chunks & \case
Nothing ->
discard
Just res ->
fromByteString (mconcat chunks) === res
againstCereal :: (Eq a, Show a) => H.Read a -> J.Get a -> [ByteString] -> Property
againstCereal read get chunks =
consumeManyByteStrings read chunks & \res ->
J.runGet get (mconcat chunks) === maybe (Left "Not enough input") Right res
in [ testProperty "byteString" $ \a ->
againstByteString (H.byteString (max 0 a)) (D.take a),
testProperty "skip & byteString" $ \a b ->
againstByteString
(H.skip (max 0 a) *> H.byteString (max 0 b))
(D.take b . D.drop a),
testProperty "skipWhile" $ \a b ->
againstByteString
(H.skipWhile (< a) *> H.byteString (max 0 b))
(D.dropWhile (< a) >>> D.take b),
testProperty "byteStringWhile" $ \a ->
againstByteString
(H.byteStringWhile (< a))
(D.takeWhile (< a)),
testProperty "asciiIntegral" $
forAll (arbitrary @Int >>= splitRandomly . fromString . (<> " ") . show . abs) $
againstByteString (H.asciiIntegral) (read . I.unpack),
testProperty "int16InBe" $
forAll (arbitrary @Int16 >>= splitRandomly . J.runPut . J.putInt16be) $
againstCereal H.int16InBe J.getInt16be,
testProperty "int32InBe" $
forAll (arbitrary @Int32 >>= splitRandomly . J.runPut . J.putInt32be) $
againstCereal H.int32InBe J.getInt32be,
testProperty "int64InBe" $
forAll (arbitrary @Int64 >>= splitRandomly . J.runPut . J.putInt64be) $
againstCereal H.int64InBe J.getInt64be,
testProperty "nullTerminatedByteString" $
againstByteString
(H.nullTerminatedByteString)
(D.takeWhile (/= 0)),
testCase "Pure does not hold on empty input" $
assertEqual "" (Just ()) (consumeManyByteStrings (pure ()) [""]),
testCase "Monadic composition" $
do
let input = J.runPut (J.putInt32be 1 <> J.putInt32be 2)
consumeManyByteStrings (liftM2 (,) H.int32InBe H.int32InBe) [input]
& assertEqual "" (Just (1, 2)),
testCase "Applicative composition" $
do
let input = J.runPut (J.putInt32be 1 <> J.putInt32be 2)
consumeManyByteStrings (liftA2 (,) H.int32InBe H.int32InBe) [input]
& assertEqual "" (Just (1, 2)),
testProperty "Composition over chunks" $
let gen = do
(a, b, c) <- arbitrary
splitRandomly (J.runPut (J.putInt16be a <> J.putInt32be b <> J.putInt32be c))
in forAll gen $
againstCereal
((,,) <$> H.int16InBe <*> H.int32InBe <*> H.int32InBe)
((,,) <$> J.getInt16be <*> J.getInt32be <*> J.getInt32be)
]
]
parsing :: TestTree
parsing =
testGroup "Parsing" $
let assertParsesTo expected input parser =
assertEqual "" (Right expected) (A.parse input (fmap Right parser) (Left . Left) (Left . Right))
in [ testCase "bytesWhile" $ assertParsesTo "123" "123456" $ G.bytesWhile (< 52),
testCase "bytesWhile on full input" $ assertParsesTo "123456" "123456" $ G.bytesWhile (< 59),
testCase "skipWhile on full input" $ assertParsesTo () "123456" $ G.skipWhile (< 59)
]
pokeThenPeek :: B.Poke a -> C.Peek a -> Maybe (a -> a)
pokeThenPeek (B.Poke pokeSize pokeIO) (C.Peek peekSize peekIO) =
if pokeSize /= peekSize
then Nothing
else Just $ \input -> unsafePerformIO $ do
fp <- mallocForeignPtrBytes pokeSize
withForeignPtr fp $ \p -> do
pokeIO p input
peekIO p
pokeAndPeek :: E.PokeAndPeek input output -> input -> output
pokeAndPeek (E.PokeAndPeek size poke peek) input =
unsafePerformIO $ do
fp <- mallocForeignPtrBytes size
withForeignPtr fp $ \p -> do
poke p input
peek p
splitRandomly :: ByteString -> Gen [ByteString]
splitRandomly =
fmap reverse . buildReverse []
where
buildReverse chunks input =
if D.null input
then pure chunks
else do
chunkLength <- choose (0, D.length input)
D.splitAt chunkLength input & \(l, r) -> do
buildReverse (l : chunks) r