transfer-db-0.3.1.0: tests/Database/TransferDB/DumpDB/FormatSpec.hs
module Database.TransferDB.DumpDB.FormatSpec (spec) where
import Test.Hspec
import Test.QuickCheck
import Database.TransferDB.DumpDB.Format
import Data.Store (Store(size, peek, poke),
Size(ConstSize, VarSize),
PeekException,
encode,
decode)
import Data.Time.Clock (UTCTime(UTCTime), secondsToDiffTime)
import Data.Time.Calendar (Day(ModifiedJulianDay))
import Data.Word (Word8, Word16, Word32)
import Data.Int (Int16, Int64)
import Foreign.C.Types (CShort(CShort), CLong(CLong))
import Foreign.Marshal (allocaBytes)
import Foreign.Storable (Storable (pokeElemOff))
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString as B
import System.Endian (getSystemEndianness, Endianness(BigEndian))
import SQL.CLI (SQLINTEGER)
spec :: Spec
spec = do
describe "Version" $ do
it "has constant size of 1 byte" $ do
(size::Size Version) `shouldBe` ConstSize 1
context "when using value V1" $ do
it "encodes to a bytestring of length 1 byte" $ do
(B.length $ encode V1) `shouldBe` 1
it "encodes to a bytestring that has a single octet with value 1" $ do
encode V1 `shouldBe` B.pack [1]
describe "decode" $ do
it "decodes the encoded value to the same value" $ do
(decode $ encode V1) `shouldBe` Right V1
describe "HeaderV1" $ do
it "has the size given by the size of its components" $ property $
\ header -> let maxChunkSizeS = sizeOf (size :: Size SQLINTEGER) (hv1_MaxChunkSize header)
timestampS = sizeOf (size :: Size UTCTime) (hv1_Timestamp header)
descriptionS = sizeOf (size :: Size C.ByteString) (hv1_Description header)
in case (size::Size HeaderV1) of
ConstSize _ -> False
VarSize f -> f header == maxChunkSizeS + timestampS + descriptionS
it "encodes to a bytestring with the length equal to the size returned by sizeOf" $ property $
\ header -> (B.length $ encode (header::HeaderV1)) == sizeOf (size :: Size HeaderV1) header
describe "decode" $ do
it "decodes an encoded value to the same value" $ property $
\ header -> (decode $ encode (header::HeaderV1)) == Right header
describe "SchemaV1" $ do
it "encodes to a bytestring with the length equal to the size returned by sizeOf" $ property $
\ sch -> (B.length $ encode (sch::SchemaV1)) == sizeOf (size :: Size SchemaV1) sch
describe "decode" $ do
it "decodes an encoded value to the same value" $ property $
\ sch -> (decode $ encode (sch::SchemaV1)) == Right sch
describe "FieldInfoV1" $ do
it "encodes to a bytestring with the length equal to the size returned by sizeOf" $ property $
\ fld -> (B.length $ encode (fld::FieldInfoV1)) == sizeOf (size :: Size FieldInfoV1) fld
describe "decode" $ do
it "decodes an encoded value to the same value" $ property $
\ fld -> (decode $ encode (fld::FieldInfoV1)) == Right fld
describe "RecordIndicator" $ do
it "has constant size of 1 byte" $ do
(size::Size RecordIndicator) `shouldBe` ConstSize 1
context "when using value RI" $ do
it "encodes to a bytstring of length 1" $ do
(B.length $ encode RI) `shouldBe` 1
it "encodes to a bytestring containing a single byte with value 255" $ do
encode RI `shouldBe` B.pack [255]
describe "decode" $ do
it "decodes the encoded value back to the original value" $ do
(decode $ encode RI) `shouldBe` Right RI
context "when using value EOT" $ do
it "encodes to a bytstring of length 1" $ do
(B.length $ encode EOT) `shouldBe` 1
it "encodes to a bytestring containing a single byte with value 0" $ do
encode EOT `shouldBe` B.pack [0]
describe "decode" $ do
it "decodes the encoded value back to the original value" $ do
(decode $ encode EOT) `shouldBe` Right EOT
describe "sizeOf" $ do
it "should be 1 for any Word8" $ property $
\ int -> sizeOf (size :: Size Word8) int == 1
describe "writeVersion" $ do
it "shouldBe the bytes '11' for V1" $ do
writeVersion V1 `shouldBe` B.pack [1,1]
describe "writeHeader" $ do
it "should write the length of the header as first 2 bytes" $ property $
\ h -> let h' = writeHeader h
in
((decode (B.take 2 h')) :: Either PeekException Word16) == (Right $ fromIntegral $ sizeOf (size :: Size HeaderV1) h)
it "should encode the header after the length" $ property $
\ h -> let h' = writeHeader h
in
decode (B.drop 2 h') == Right h
it "should create a ByteString with the length 2 + the length encoded as the first 2 bytes" $ property $
\ h -> let h' = writeHeader h
in
(Right $ fromIntegral $ (B.length h') - 2) == ((decode (B.take 2 h')) :: Either PeekException Word16)
describe "writeSchema" $ do
it "should write the length of the encoded schema as the first 4 bytes" $ property $
\ s -> let s' = writeSchema s
in
((decode (B.take 4 s')) :: Either PeekException Word32) == (Right $ fromIntegral $ sizeOf (size :: Size SchemaV1) s)
it "should encode the header after the length" $ property $
\ s -> let s' = writeSchema s
in
decode (B.drop 4 s') == Right s
it "should create a ByteString with the lengh 4 + the length encoded as the first 4 bytes" $ property $
\ s -> let s' = writeSchema s
in
(Right $ fromIntegral $ (B.length s') - 4) == ((decode (B.take 4 s')) :: Either PeekException Word32)
describe "writeRI" $ do
it "should return a bytestring with a sigle byte with value 255" $ do
writeRI `shouldBe` B.pack [255]
describe "writeEOT" $ do
it "should return a bytestring with a single byte with value 255" $ do
writeEOT `shouldBe` B.pack [0]
describe "writeNullIndicator" $ do
it "should write a Null value into a bytestring with only one byte whose value is 0" $ do
writeNullIndicator Null `shouldBe` B.pack [0]
it "should write a NotNull indicator into a bytestring with only one byte whose value is 255" $ do
writeNullIndicator NotNull `shouldBe` B.pack [255]
describe "writeChunk" $ do
context "when using 1 byte chunk size field" $ do
let lenlen = 1
it "should encode bytes [1, 2, 3, 4] to a bytestring containing bytes [4, 1, 2, 3, 4]" $ do
allocaBytes 4
(\ ptr -> do
pokeElemOff ptr 0 (1::Word8)
pokeElemOff ptr 1 (2::Word8)
pokeElemOff ptr 2 (3::Word8)
pokeElemOff ptr 3 (4::Word8)
return $ writeChunk lenlen 4 ptr
) `shouldReturn` B.pack [4, 1, 2, 3, 4]
context "when using 2 byte chunk size field" $ do
let lenlen = 2
expected = if getSystemEndianness == BigEndian then [0, 4, 1, 2, 3, 4] else [4, 0, 1, 2, 3, 4]
it ("should encode bytes [1, 2, 3, 4] to a bytestring containing bytes " ++ (show expected)) $ do
allocaBytes 4
(\ ptr -> do
pokeElemOff ptr 0 (1::Word8)
pokeElemOff ptr 1 (2::Word8)
pokeElemOff ptr 2 (3::Word8)
pokeElemOff ptr 3 (4::Word8)
return $ writeChunk lenlen 4 ptr
) `shouldReturn` B.pack expected
context "when using 4 byte chunk size field" $ do
let lenlen = 4
expected = if getSystemEndianness == BigEndian then [0, 0, 0, 4, 1, 2, 3, 4] else [4, 0, 0, 0, 1, 2, 3, 4]
it ("should encode bytes [1, 2, 3, 4] to a bytestring containing bytes " ++ (show expected)) $ do
allocaBytes 4
(\ ptr -> do
pokeElemOff ptr 0 (1::Word8)
pokeElemOff ptr 1 (2::Word8)
pokeElemOff ptr 2 (3::Word8)
pokeElemOff ptr 3 (4::Word8)
return $ writeChunk lenlen 4 ptr
) `shouldReturn` B.pack expected
describe "writePlainBuf" $ do
it "should encode a buffer containing bytes 1, 2, 3 and 4 into a bytestring containing bytes [1,2,3,4]" $ do
allocaBytes 4
(\ ptr -> do
pokeElemOff ptr 0 (1::Word8)
pokeElemOff ptr 1 (2::Word8)
pokeElemOff ptr 2 (3::Word8)
pokeElemOff ptr 3 (4::Word8)
return $ writePlainBuf ptr 4
) `shouldReturn` B.pack [1, 2, 3, 4]
instance Arbitrary HeaderV1 where
arbitrary = HeaderV1 <$> arbitrary <*> arbitrary <*> arbitrary
instance Arbitrary SchemaV1 where
arbitrary = SchemaV1 <$> arbitrary <*> arbitrary <*> (listOf arbitrary)
instance Arbitrary FieldInfoV1 where
arbitrary = FieldInfoV1 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
instance Arbitrary UTCTime where
arbitrary = do
day <- arbitrary
seconds <- choose (0, 86400)
return $ UTCTime (ModifiedJulianDay day) (secondsToDiffTime seconds)
instance Arbitrary C.ByteString where
arbitrary = listOf arbitrary >>= return.C.pack