packages feed

flatbuffers-0.4.0.0: test/FlatBuffers/ReadSpec.hs

{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}

module FlatBuffers.ReadSpec where

import Control.Exception (evaluate)
import Data.Functor (($>))
import Data.Int
import Data.List qualified as List
import Data.Maybe qualified as Maybe
import Data.Text qualified as Text
import Data.Text.Read qualified as Text
import Examples
import FlatBuffers.Internal.Read
import FlatBuffers.Internal.Write
import FlatBuffers.Vector qualified as Vec
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import TestImports

spec :: Spec
spec =
  describe "read" $ do
    it "fails when buffer is exhausted" $
      decode @() "" `shouldBeLeft` "not enough bytes"

    it "fails when decoding string with invalid UTF-8 bytes" $ do
      let text = Vec.singleton 255
      table <- evalRight $ decode $ encode $ writeTable
        [ missing, missing, missing, missing
        , missing, missing, missing, missing
        , missing, missing, missing
        , writeVectorWord8TableField text
        ]
      primitivesL table `shouldBeLeftAndExpect` \errMsg -> do
        errMsg `shouldStartWith` "UTF8 decoding error (byte 255)"
        errMsg `shouldEndWith` "Invalid UTF-8 stream"

    it "fails when required field is missing" $ do
      table <- evalRight $ decode @RequiredFields $ encode $ writeTable []
      requiredFieldsA table `shouldBeLeft` "Missing required table field: a"
      requiredFieldsB table `shouldBeLeft` "Missing required table field: b"
      requiredFieldsC table `shouldBeLeft` "Missing required table field: c"
      requiredFieldsD table `shouldBeLeft` "Missing required table field: d"
      requiredFieldsE table `shouldBeLeft` "Missing required table field: e"
      requiredFieldsF table `shouldBeLeft` "Missing required table field: f"

    it "throws when union type is present, but union value is missing" $ do
      table <- evalRight $ decode $ encode $ writeTable [ writeWord8TableField 1]
      tableWithUnionUni table `shouldBeLeft` "Union: 'union type' found but 'union value' is missing."

    it "throws when union type vector is present, but union value vector is missing" $ do
      table <- evalRight $ decode @VectorOfUnions $ encode $ writeTable
        [ writeVectorWord8TableField Vec.empty
        , missing
        ]
      vectorOfUnionsXs table `shouldBeLeft` "Union vector: 'type vector' found but 'value vector' is missing."

    describe "returns `UnionUnknown` when union type is not recognized" $ do
      it "in union table fields" $ do
        let union = writeUnion 99 (writeTable [])
        table <- evalRight $ decode $ encode $ tableWithUnion $ Just union
        tableWithUnionUni table `shouldBeRightAndExpect` \case
          Just (UnionUnknown n) -> n `shouldBe` 99

      it "in union vectors" $ do
        let union = writeUnion 99 (writeTable [])

        result <- evalRight $ do
          table <- decode $ encode $ vectorOfUnions $ Just $ Vec.singleton union
          vectorOfUnionsXs table >>= \case
            Just vec -> vec `unsafeIndex` 0

        case result of
          UnionUnknown n -> n `shouldBe` 99

    describe "vectors" $ do
      let getIndex :: Table b
                  -> (Table b -> Either ReadError (Maybe (Vector a)))
                  -> (Vector a -> Int32 -> Either ReadError a)
                  -> Int32
                  -> Either ReadError a
          getIndex table getVector indexFn ix = do
            vec <- getVector table
            Maybe.fromJust vec `indexFn` ix


      let testNegativeIndex table getVector =
            (case getIndex table getVector Vec.index (-1) of
              Right a -> evaluate a $> ()
              Left e  -> evaluate e $> ()
            ) `shouldThrow` errorCall "FlatBuffers.Internal.Read.index: negative index: -1"

      let testLargeIndex table getVector =
            (case getIndex table getVector Vec.index 98 of
              Right a -> evaluate a $> ()
              Left e  -> evaluate e $> ()
            ) `shouldThrow` errorCall "FlatBuffers.Internal.Read.index: index too large: 98"

      let testInvalidUnsafeIndex table getVector = do
            case getIndex table getVector Vec.unsafeIndex 100 of
              Right a -> evaluate a $> ()
              Left e  -> evaluate e $> ()
            case getIndex table getVector Vec.unsafeIndex (-100) of
              Right a -> evaluate a $> ()
              Left e  -> evaluate e $> ()

      describe "of primitives" $ do
        let Right table = decode $ encode $ vectors
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)

        it "`unsafeIndex` does not throw when index is negative / too large" $ do
          testInvalidUnsafeIndex table vectorsA
          testInvalidUnsafeIndex table vectorsB
          testInvalidUnsafeIndex table vectorsC
          testInvalidUnsafeIndex table vectorsD
          testInvalidUnsafeIndex table vectorsE
          testInvalidUnsafeIndex table vectorsF
          testInvalidUnsafeIndex table vectorsG
          testInvalidUnsafeIndex table vectorsH
          testInvalidUnsafeIndex table vectorsI
          testInvalidUnsafeIndex table vectorsJ
          testInvalidUnsafeIndex table vectorsK
          testInvalidUnsafeIndex table vectorsL

        it "`index` throws when index is negative" $ do
          testNegativeIndex table vectorsA
          testNegativeIndex table vectorsB
          testNegativeIndex table vectorsC
          testNegativeIndex table vectorsD
          testNegativeIndex table vectorsE
          testNegativeIndex table vectorsF
          testNegativeIndex table vectorsG
          testNegativeIndex table vectorsH
          testNegativeIndex table vectorsI
          testNegativeIndex table vectorsJ
          testNegativeIndex table vectorsK
          testNegativeIndex table vectorsL

        it "`index` throws when index is too large" $ do
          testLargeIndex table vectorsA
          testLargeIndex table vectorsB
          testLargeIndex table vectorsC
          testLargeIndex table vectorsD
          testLargeIndex table vectorsE
          testLargeIndex table vectorsF
          testLargeIndex table vectorsG
          testLargeIndex table vectorsH
          testLargeIndex table vectorsI
          testLargeIndex table vectorsJ
          testLargeIndex table vectorsK
          testLargeIndex table vectorsL

        it "`take` and `drop` are consistent with Data.List.take and Data.List.drop" $
          hedgehog do
            listWord8  <- forAll $ Gen.list (Range.linear 0 20) (Gen.word8  (Range.linear 0 20))
            listWord16 <- forAll $ Gen.list (Range.linear 0 20) (Gen.word16 (Range.linear 0 20))
            listWord32 <- forAll $ Gen.list (Range.linear 0 20) (Gen.word32 (Range.linear 0 20))
            listWord64 <- forAll $ Gen.list (Range.linear 0 20) (Gen.word64 (Range.linear 0 20))
            listInt8   <- forAll $ Gen.list (Range.linear 0 20) (Gen.int8   (Range.linear -20 20))
            listInt16  <- forAll $ Gen.list (Range.linear 0 20) (Gen.int16  (Range.linear -20 20))
            listInt32  <- forAll $ Gen.list (Range.linear 0 20) (Gen.int32  (Range.linear -20 20))
            listInt64  <- forAll $ Gen.list (Range.linear 0 20) (Gen.int64  (Range.linear -20 20))
            listFloat  <- forAll $ Gen.list (Range.linear 0 20) (Gen.float  (Range.linearFrac -20 20))
            listDouble <- forAll $ Gen.list (Range.linear 0 20) (Gen.double (Range.linearFrac -20 20))
            listBool   <- forAll $ Gen.list (Range.linear 0 20) Gen.bool
            listText   <- forAll $ Gen.list (Range.linear 0 20) (Gen.text (Range.singleton 3) Gen.alpha)

            n <- forAll $ Gen.int32 (Range.linearFrom 0 -10 30)

            table <- evalEither $ decode $ encode $ vectors
              (Just (Vec.fromList' listWord8))
              (Just (Vec.fromList' listWord16))
              (Just (Vec.fromList' listWord32))
              (Just (Vec.fromList' listWord64))
              (Just (Vec.fromList' listInt8))
              (Just (Vec.fromList' listInt16))
              (Just (Vec.fromList' listInt32))
              (Just (Vec.fromList' listInt64))
              (Just (Vec.fromList' listFloat))
              (Just (Vec.fromList' listDouble))
              (Just (Vec.fromList' listBool))
              (Just (Vec.fromList' listText))

            prop_takeConsistency n listWord8  (vectorsA table) pure
            prop_takeConsistency n listWord16 (vectorsB table) pure
            prop_takeConsistency n listWord32 (vectorsC table) pure
            prop_takeConsistency n listWord64 (vectorsD table) pure
            prop_takeConsistency n listInt8   (vectorsE table) pure
            prop_takeConsistency n listInt16  (vectorsF table) pure
            prop_takeConsistency n listInt32  (vectorsG table) pure
            prop_takeConsistency n listInt64  (vectorsH table) pure
            prop_takeConsistency n listFloat  (vectorsI table) pure
            prop_takeConsistency n listDouble (vectorsJ table) pure
            prop_takeConsistency n listBool   (vectorsK table) pure
            prop_takeConsistency n listText   (vectorsL table) pure

            prop_dropConsistency n listWord8  (vectorsA table) pure
            prop_dropConsistency n listWord16 (vectorsB table) pure
            prop_dropConsistency n listWord32 (vectorsC table) pure
            prop_dropConsistency n listWord64 (vectorsD table) pure
            prop_dropConsistency n listInt8   (vectorsE table) pure
            prop_dropConsistency n listInt16  (vectorsF table) pure
            prop_dropConsistency n listInt32  (vectorsG table) pure
            prop_dropConsistency n listInt64  (vectorsH table) pure
            prop_dropConsistency n listFloat  (vectorsI table) pure
            prop_dropConsistency n listDouble (vectorsJ table) pure
            prop_dropConsistency n listBool   (vectorsK table) pure
            prop_dropConsistency n listText   (vectorsL table) pure


      describe "of structs" $ do
        let Right table = decode $ encode $ vectorOfStructs
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)
              (Just Vec.empty)

        it "`unsafeIndex` does not throw when index is negative / too large" $
          testInvalidUnsafeIndex table vectorOfStructsAs

        it "`index` throws when index is negative" $
          testNegativeIndex table vectorOfStructsAs

        it "`index` throws when index is too large" $
          testLargeIndex table vectorOfStructsAs

        it "`take` and `drop` are consistent with Data.List.take and Data.List.drop" $
          hedgehog $ do
            listInt16 <- forAll $ Gen.list (Range.linear 0 20) (Gen.int16 (Range.linear -20 20))
            n <- forAll $ Gen.int32 (Range.linearFrom 0 -10 30)

            table <- evalEither $ decode $ encode $ vectorOfStructs
              Nothing
              (Just (Vec.fromList' (struct2 <$> listInt16)))
              Nothing
              Nothing

            prop_takeConsistency n listInt16 (vectorOfStructsBs table) struct2X
            prop_dropConsistency n listInt16 (vectorOfStructsBs table) struct2X

      describe "of tables" $ do
        let Right table = decode $ encode $ vectorOfTables
              (Just Vec.empty)

        it "`unsafeIndex` does not throw when index is negative / too large" $
          testInvalidUnsafeIndex table vectorOfTablesXs

        it "`index` throws when index is negative" $
          testNegativeIndex table vectorOfTablesXs

        it "`index` throws when index is too large" $
          testLargeIndex table vectorOfTablesXs

        it "`take` and `drop` are consistent with Data.List.take and Data.List.drop" $
          hedgehog $ do
            listInt32 <- forAll $ Gen.list (Range.linear 0 20) (Gen.int32 (Range.linear -20 20))
            n <- forAll $ Gen.int32 (Range.linearFrom 0 -10 30)

            table <- evalEither $ decode $ encode $ vectorOfTables
              (Just (Vec.fromList' (axe . Just <$> listInt32)))

            prop_takeConsistency n listInt32 (vectorOfTablesXs table) axeY
            prop_dropConsistency n listInt32 (vectorOfTablesXs table) axeY

      describe "of unions" $ do
        let Right table = decode $ encode $ vectorOfUnions
              (Just Vec.empty)

        it "`unsafeIndex` does not throw when index is negative / too large" $
          testInvalidUnsafeIndex table vectorOfUnionsXs

        it "`index` throws when index is negative" $
          testNegativeIndex table vectorOfUnionsXs

        it "`index` throws when index is too large" $
          testLargeIndex table vectorOfUnionsXs

        it "`take` and `drop` are consistent with Data.List.take and Data.List.drop" $
          hedgehog $ do
            listOfPairs :: [(String, Int32)] <- forAll $ Gen.list (Range.linear 0 20) $ do
              unionType <- Gen.element ["Axe", "Sword"]
              unionVal <- Gen.int32 (Range.linear -20 20)
              pure (unionType, unionVal)

            n <- forAll $ Gen.int32 (Range.linearFrom 0 -10 30)

            let pairToUnion :: (String, Int32) -> WriteUnion Weapon
                pairToUnion = \case
                  ("Axe", val)   -> weaponAxe (axe (Just val))
                  ("Sword", val) -> weaponSword (sword (Just (Text.pack (show val))))

            let unionToPair :: Union Weapon -> Either ReadError (String, Int32)
                unionToPair = \case
                  Union (WeaponAxe axe) -> do
                    val <- axeY axe
                    pure ("Axe", val)
                  Union (WeaponSword sword) -> do
                    textValMaybe <- swordX sword
                    case textValMaybe of
                      Just textVal ->
                        case Text.signed Text.decimal textVal of
                          Right (intVal, _) ->
                            pure ("Sword", intVal)

            table <- evalEither $ decode $ encode $ vectorOfUnions $ Just $
              Vec.fromList' (pairToUnion <$> listOfPairs)

            prop_takeConsistency n listOfPairs (vectorOfUnionsXs table) unionToPair
            prop_dropConsistency n listOfPairs (vectorOfUnionsXs table) unionToPair


prop_takeConsistency ::
  (Eq a, Show a, VectorElement b)
  => Int32
  -> [a]
  -> Either ReadError (Maybe (Vector b))
  -> (b -> Either ReadError a)
  -> PropertyT IO ()
prop_takeConsistency n list vec extract = do
  Just vec <- evalEither vec
  (Vec.toList (Vec.take n vec) >>= traverse extract) === Right (List.take (fromIntegral n) list)
  Vec.length (Vec.take n vec) === fromIntegral (List.length (List.take (fromIntegral n) list))

prop_dropConsistency ::
  (Eq a, Show a, VectorElement b)
  => Int32
  -> [a]
  -> Either ReadError (Maybe (Vector b))
  -> (b -> Either ReadError a)
  -> PropertyT IO ()
prop_dropConsistency n list vec extract = do
  Just vec <- evalEither vec
  (Vec.toList (Vec.drop n vec) >>= traverse extract) === Right (List.drop (fromIntegral n) list)
  Vec.length (Vec.drop n vec) === fromIntegral (List.length (List.drop (fromIntegral n) list))