binary-ext-2.0: test/Data/Conduit/Parsers/Binary/Get/Spec.hs
--
-- Copyright 2017 Warlock <internalmike@gmail.com>
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
module Data.Conduit.Parsers.Binary.Get.Spec
( tests
) where
import Control.Applicative
import Control.Monad.Error.Class
import Control.Monad.Loops
import Control.Monad.Trans.Class
import Control.Monad.Trans.State.Strict
import Data.Bits
import qualified Data.ByteString as S (ByteString)
import qualified Data.ByteString as SB hiding (ByteString, head, last, init, tail)
import Data.ByteString.Lazy (ByteString)
import Data.Char
import Data.Conduit
import qualified Data.Conduit.Combinators as N
import Data.Conduit.Lift
import Data.Functor.Identity
import Data.Semigroup hiding (Option)
import Data.Void
import Data.Word
import Test.HUnit.Base hiding (Label)
import qualified Data.Conduit.Parsers as G
import qualified Data.Conduit.Parsers.Binary.ByteOffset as G
import Data.Conduit.Parsers.Binary.Get hiding (runGet)
import qualified Data.Conduit.Parsers.Binary.Get as G (runGet)
import qualified Data.Conduit.Parsers.GetC as G
tests :: Test
tests = TestList
[ TestCase getBytes1
, TestCase getBytes2
, TestCase testSkip
, TestCase eofError
, TestCase eofOrNotEof
, TestCase testIsolateOverAlternativeIsolateNotEnough
, TestCase testIsolateOverAlternativeIsolateExactly
, TestCase testIsolateOverAlternativeIsolateEnough
, TestCase testIsolateOverAlternativeIsolateEnoughButEof
, TestCase testIsolateIsolateEnoughButEof
, TestCase testIsolateIsolateEnoughButEofEarly
, TestCase testAlternativeRollback
, TestCase testRecords
, TestCase testLeftoversInIsolate
, TestCase testSkipUntilZero
, TestCase testErrorMap
]
runGet :: Monad m => GetM G.ByteOffset i o e m a -> ConduitM i o m (Either e a, Word64)
runGet !g = (\(!r, !s) -> (r, G.decodingElemsRead s)) <$> G.runGetC (G.startDecoding $ G.ByteOffset 0) g
testInput1 :: [S.ByteString]
testInput1 =
[ "\x12\x13\x14"
, "\x15\x18\xF3"
, ""
]
testInput2 :: [S.ByteString]
testInput2 =
[ "\x12\x13\x14"
, "\x15\x18\xF3"
, "\0"
]
testInput3 :: [S.ByteString]
testInput3 =
[ "\x12\x13"
, "\x15\x18\xF3"
, "\0"
]
testInput4 :: [S.ByteString]
testInput4 =
[ "AB"
, "C"
]
testInput5 :: [S.ByteString]
testInput5 =
[ "AB"
, "CDE"
]
testInput6 :: [S.ByteString]
testInput6 =
[ "\x01\x02\x03\x04\x05\x06"
, "\x07\x08\x09\x0A\x0B\x0C"
]
testInput7 :: [S.ByteString]
testInput7 =
[ "A"
, "B"
, "C"
]
ensureEof :: e -> Get e ()
ensureEof e = do
eof <- N.nullE
if eof then return () else throwError e
get1 :: (DefaultDecodingState s, Monad m) => GetM s S.ByteString Word16 Bool m ()
get1 = do
yield =<< mapError (const False) getWord16le
yield =<< mapError (const False) getWord16le
yield =<< mapError (const False) getWord16be
ensureEof True
get2 :: Get () Word64
get2 = do
skip 3
bytesRead
getTailBytes :: Get () S.ByteString
getTailBytes = do
r <- getByteString 3
ensureEof ()
return r
getBytes1 :: Assertion
getBytes1 = do
let ((!e, !c), !r) = runIdentity $ N.yieldMany testInput1 $$ (runGet get1 `fuseBoth` N.sinkList)
assertEqual "" (Right ()) e
assertEqual "" [0x13 `shiftL` 8 .|. 0x12, 0x15 `shiftL` 8 .|. 0x14, 0x18 `shiftL` 8 .|. 0xF3] r
assertEqual "" 6 c
getBytes2 :: Assertion
getBytes2 = do
let ((!e, !c), !r) = runIdentity $ N.yieldMany testInput2 $$ (runGet get1 `fuseBoth` N.sinkList)
assertEqual "" (Left True) e
assertEqual "" [0x13 `shiftL` 8 .|. 0x12, 0x15 `shiftL` 8 .|. 0x14, 0x18 `shiftL` 8 .|. 0xF3] r
assertEqual "" 6 c
testSkip :: Assertion
testSkip = do
let (!e, !c) = runIdentity $ N.yieldMany testInput3 $$ runGet get2
assertEqual "" (Right 3) e
assertEqual "" 3 c
eofError :: Assertion
eofError = do
let (!e, !c) = runIdentity $ N.yieldMany testInput4 $$ runGet getInt64host
assertEqual "" (Left ()) e
assertEqual "" 3 c
eofOrNotEof :: Assertion
eofOrNotEof = do
let (!e, !c) = runIdentity $ N.yieldMany testInput4 $$ runGet (Right <$> getInt64host <|> Left <$> getTailBytes)
assertEqual "" (Right $ Left "ABC") e
assertEqual "" 3 c
testIsolateOverAlternativeIsolateNotEnough :: Assertion
testIsolateOverAlternativeIsolateNotEnough = do
let
(!e, !c) = runIdentity $ N.yieldMany testInput5
$$ runGet (isolate 2 $ Right <$> getInt32le <|> Left <$> getWord8)
assertEqual "" (Left $ Left $ Just 1) e
assertEqual "" 1 c
testIsolateOverAlternativeIsolateExactly :: Assertion
testIsolateOverAlternativeIsolateExactly = do
let
(!e, !c) = runIdentity $ N.yieldMany testInput5
$$ runGet (isolate 1 $ Right <$> getInt32le <|> Left <$> getWord8)
assertEqual "" (Right $ Left $ fromIntegral $ ord 'A') e
assertEqual "" 1 c
testIsolateOverAlternativeIsolateEnough :: Assertion
testIsolateOverAlternativeIsolateEnough = do
let
(!e, !c) = runIdentity $ N.yieldMany testInput5
$$ runGet (isolate 4 $ Right <$> getInt32le <|> Left <$> getWord8)
assertEqual "" (Right $ Right 1145258561) e
assertEqual "" 4 c
testIsolateOverAlternativeIsolateEnoughButEof :: Assertion
testIsolateOverAlternativeIsolateEnoughButEof = do
let
(!e, !c) = runIdentity $ N.yieldMany testInput4
$$ runGet (isolate 4 $ Right <$> getInt32le <|> Left <$> getWord8)
assertEqual "" (Left $ Left Nothing) e
assertEqual "" 0 c
testIsolateIsolateEnoughButEof :: Assertion
testIsolateIsolateEnoughButEof = do
let
(!e, !c) = runIdentity $ N.yieldMany testInput4
$$ runGet (isolate 4 getWord8)
assertEqual "" (Left $ Left $ Just 1) e
assertEqual "" 1 c
testIsolateIsolateEnoughButEofEarly :: Assertion
testIsolateIsolateEnoughButEofEarly = do
let
(!e, !c) = runIdentity $ N.yieldMany testInput7
$$ runGet (isolate 4 $ getWord8 >> getWord8 >> getWord8 >> getWord8)
assertEqual "" (Left $ Left Nothing) e
assertEqual "" 0 c
testAlternativeRollback :: Assertion
testAlternativeRollback = do
let (!e, !c) = runIdentity $ N.yieldMany testInput6 $$ runGet ((skip 9 >> throwError ()) <|> getWord64le)
assertEqual "" (Right $ 0x01 .|. 0x02 `shiftL` 8 .|. 0x03 `shiftL` 16 .|. 0x04 `shiftL` 24 .|. 0x05 `shiftL` 32 .|. 0x06 `shiftL` 40 .|. 0x07 `shiftL` 48 .|. 0x08 `shiftL` 56) e
assertEqual "" 8 c
recordBody :: Get () [Word64]
recordBody = whileM (not <$> N.nullE) $ mapError (const ()) $ isolate 8 getWord64le
record :: Word64 -> Get (Either (Maybe Word64) ()) [Word64]
record z = isolate z recordBody
records :: (DefaultDecodingState s, Monad m) => GetM s S.ByteString [Word64] (Either (Maybe Word64) ()) m ()
records = do
yield =<< record 24
yield =<< record 16
yield =<< record 8
recordsInput :: [S.ByteString]
recordsInput =
[ "0123456701234567"
, "01234567012345670123456701234567"
]
testRecords :: Assertion
testRecords = do
let ((!e, !c), !r) = runIdentity $ N.yieldMany recordsInput $$ (runGet records `fuseBoth` N.sinkList)
assertEqual (show c) (Right ()) e
assertEqual "" [[3978425819141910832, 3978425819141910832, 3978425819141910832], [3978425819141910832, 3978425819141910832], [3978425819141910832]] r
assertEqual "" 48 c
takeE :: Monad m => Int -> ConduitM S.ByteString o m S.ByteString
takeE !n =
go SB.empty 0
where
go consumed !consumed_length
| consumed_length >= n = return consumed
| otherwise = do
!mi <- await
case mi of
Nothing -> error "takeE"
Just !i -> do
let !gap = n - consumed_length
if gap >= SB.length i
then do
go (consumed <> i) (consumed_length + fromIntegral (SB.length i))
else do
let (!got, !rest) = SB.splitAt gap i
leftover rest
return (consumed <> got)
testLeftoversInIsolate :: Assertion
testLeftoversInIsolate = do
let !i = isolate 4 $ (leftover =<< takeE 4) >> skip 2
let
!g = do
catchError i $ const $ return ()
!r <- mapError Right $ getByteString 2
ensureEof $ Right ()
return r
let (!e, !c) = runIdentity $ yield "ABCD" $$ runGet g
assertEqual "" (Right "CD") e
assertEqual "" 4 c
skipUntilZero :: Get e Bool
skipUntilZero = G.getC $ flip runStateC $ untilJust $ do
!m_inp <- await
case m_inp of
Nothing -> return $ Just $ Right False
Just !inp -> do
case SB.elemIndex 0 inp of
Nothing -> do
lift $ modify' $ G.decoded inp
return Nothing
Just !i -> do
let (!h, !t) = SB.splitAt i inp
leftover t
lift $ modify' $ G.decoded h
return $ Just $ Right True
testZeroInput1 :: [S.ByteString]
testZeroInput1 =
[ "0123"
, "45\0zx"
, "8"
]
testSkipUntilZero :: Assertion
testSkipUntilZero = do
let (!r, !c) = runIdentity $ N.yieldMany testZeroInput1 $$ runGet (skipUntilZero >> getRemainingLazyByteString)
assertEqual "" ((Right "\0zx8") :: Either Void ByteString) r
assertEqual "" 10 c
w32 :: Get String Word32
w32 = getWord32le ?>> ("Unexpected EOF at " ++) <$> show <$> bytesRead
testErrorMap :: Assertion
testErrorMap = do
let !r = runIdentity $ N.yieldMany [] $$ G.runGet w32
assertEqual "" (Left "Unexpected EOF at 0") r