packages feed

toxcore-0.2.11: test/Network/Tox/EncodingSpec.hs

{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Trustworthy         #-}
module Network.Tox.EncodingSpec
    ( spec
    , binarySpec
    , binaryGetPutSpec
    , bitEncodingSpec
    , readShowSpec
    , rpcSpec
    , expectDecoded
    , expectDecoderFail
    ) where

import           Control.Monad.IO.Class     (liftIO)
import           Data.MessagePack           (MessagePack)
import           Network.MessagePack.Client (Client)
import           Test.Hspec
import           Test.QuickCheck            (Arbitrary)
import qualified Test.QuickCheck            as QC

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)


-- | Limit the number of tests we do with the encoders/decoders.
--
-- These are fairly expensive, and running very large tests for them is probably
-- not very valuable.
property :: QC.Testable prop => prop -> QC.Property
property = QC.withMaxSuccess 50 . QC.property


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 "should have a non-nullable packet grammar" $
      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) =
    describe "MessagePack" $
        it "encodes and decodes correctly" $ property $ \x ->
            decodeA (encodeA x) `shouldBe` Just x

  where
    encodeA = Binary.encode :: a -> ByteString.ByteString
    decodeA = Binary.decode :: ByteString.ByteString -> Maybe a