packages feed

hstox-0.0.1: src/testsuite/Network/Tox/EncodingSpec.hs

{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Trustworthy         #-}
module Network.Tox.EncodingSpec where

import           Control.Monad.IO.Class     (liftIO)
import           Data.MessagePack           (MessagePack)
import           Network.MessagePack.Client (Client)
import qualified Network.Tox.RPCTest        as RPC
import           Test.Hspec
import           Test.QuickCheck            (Arbitrary, property)

import           Data.Binary                (Binary)
import qualified Data.Binary                as Binary (get, put)
import qualified Data.Binary.Bits.Get       as Bits (BitGet, runBitGet)
import qualified Data.Binary.Bits.Put       as Bits (BitPut, runBitPut)
import qualified Data.Binary.Get            as Binary (Decoder (..), Get,
                                                       pushChunk, runGet,
                                                       runGetIncremental)
import qualified Data.Binary.Put            as Binary (Put, runPut)
import qualified Data.ByteString            as ByteString
import qualified Data.ByteString.Lazy       as LazyByteString
import           Data.Proxy                 (Proxy (..))
import           Data.Typeable              (Typeable)
import           Data.Word                  (Word64, Word8)

import qualified Network.Tox.Binary         as Binary
import           Network.Tox.Encoding       (BitEncoding, bitGet, bitPut)


spec :: Spec
spec =
  rpcSpec (Proxy :: Proxy Word64)


expectDecoded :: (Binary a, Eq a, Show a) => [Word8] -> a -> Expectation
expectDecoded bytes expected =
  Binary.runGet Binary.get (LazyByteString.pack bytes) `shouldBe` expected


expectDecoderFail :: Binary.Get a -> [Word8] -> String -> Expectation
expectDecoderFail getA bytes expectedMessage =
  let decoder = Binary.runGetIncremental getA in
  case Binary.pushChunk decoder $ ByteString.pack bytes of
    Binary.Fail _ _ msg -> msg `shouldContain` expectedMessage
    Binary.Partial _    -> expectationFailure "Not enough input to reach failure"
    Binary.Done {}      -> expectationFailure "Input unexpectedly yielded a valid value"


binaryEncodeAndDecode :: (Eq a, Show a) => Binary.Get a -> (a -> Binary.Put) -> a -> Expectation
binaryEncodeAndDecode getA putA expected =
  let bytes = LazyByteString.toStrict $ Binary.runPut $ putA expected in
  finish $ Binary.pushChunk (Binary.runGetIncremental getA) bytes

  where
    finish = \case
      Binary.Fail _ _ msg            -> expectationFailure msg
      Binary.Partial next            -> finish $ next Nothing
      Binary.Done remaining _ output -> do
        remaining `shouldBe` ByteString.empty
        output `shouldBe` expected


binaryGetPutSpec :: (Arbitrary a, Eq a, Show a) => String -> Binary.Get a -> (a -> Binary.Put) -> Spec
binaryGetPutSpec name getA putA =
  describe name $ do
    it "decodes encoded protocols correctly" $
      property $ binaryEncodeAndDecode getA putA

    it "handles arbitrary input" $
      property $ \bytes ->
        let
          finish = \case
            Binary.Fail {}         -> return ()
            Binary.Partial f       -> finish $ f Nothing
            Binary.Done _ _ output -> binaryEncodeAndDecode getA putA output
        in
        finish $ Binary.pushChunk (Binary.runGetIncremental getA) $ ByteString.pack bytes

    it "handles empty input" $
      let
        bytes = []
        decoder = Binary.runGetIncremental getA
      in
      case Binary.pushChunk decoder $ ByteString.pack bytes of
        Binary.Fail _ _ msg -> expectationFailure msg
        Binary.Partial _    -> return ()
        Binary.Done {}      -> expectationFailure "Done with empty input; packet grammar appears to be nullable"


binarySpec :: (Arbitrary a, Eq a, Show a, Binary a) => Proxy a -> Spec
binarySpec (Proxy :: Proxy a) =
  binaryGetPutSpec "Binary.{get,put}" (Binary.get :: Binary.Get a) (Binary.put :: a -> Binary.Put)


bitEncodingSpec :: (Arbitrary a, Eq a, Show a, BitEncoding a) => Proxy a -> Spec
bitEncodingSpec (Proxy :: Proxy a) =
  let
    bitGetA = (bitGet :: Bits.BitGet a)
    bitPutA = (bitPut :: a -> Bits.BitPut ())
  in
  binaryGetPutSpec "BitEncoding.bit{Get,Put}" (Bits.runBitGet bitGetA) (Bits.runBitPut . bitPutA)


readShowSpec :: (Arbitrary a, Eq a, Show a, Read a) => Proxy a -> Spec
readShowSpec (Proxy :: Proxy a) =
  let
    showA = show :: a -> String
    readA = read :: String -> a
  in
  describe "Read/Show" $
    it "encodes and decodes correctly" $
      property $ \expected ->
        let output = readA $ showA expected in
        output `shouldBe` expected


rpcSpec :: (Arbitrary a, Eq a, Show a, Typeable a, Binary a, MessagePack a) => Proxy a -> Spec
rpcSpec (Proxy :: Proxy a) =
  let
    encodeAC = Binary.encodeC :: a -> Client ByteString.ByteString
    decodeAC = Binary.decodeC :: ByteString.ByteString -> Client (Maybe a)
    encodeA  = Binary.encode  :: a -> ByteString.ByteString
    decodeA  = Binary.decode  :: ByteString.ByteString -> Maybe a
  in

  describe "MessagePack" $ do
    it "encodes and decodes correctly" $
      property $ \expected -> RPC.runTest $ do
        encoded <- encodeAC expected
        decoded <- decodeAC encoded
        liftIO $ decoded `shouldBe` Just expected

    it "encodes arbitrary input correctly" $
      property $ \expected -> RPC.runTest $ do
        encoded <- encodeAC expected
        liftIO $ encoded `shouldBe` encodeA expected

    it "decodes arbitrary input correctly" $
      property $ \bytes -> RPC.runTest $ do
        let bs = ByteString.pack bytes
        decoded <- decodeAC bs
        liftIO $ decoded `shouldBe` decodeA bs