asif-4.0.0: test/Arbor/File/Format/Asif/WriteSpec.hs
module Arbor.File.Format.Asif.WriteSpec where
import Arbor.File.Format.Asif.Data.Ip
import Arbor.File.Format.Asif.Format
import Arbor.File.Format.Asif.Segment
import Arbor.File.Format.Asif.Write
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Class
import Control.Monad.Trans.Resource
import Data.Int
import Data.Semigroup ((<>))
import Data.Word
import System.IO.Temp (openBinaryTempFile)
import HaskellWorks.Hspec.Hedgehog
import Hedgehog
import Test.Hspec
import qualified Data.Attoparsec.ByteString as AP
import qualified Data.ByteString.Lazy as LBS
import qualified Data.IP as IP
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Encoding as T
import qualified Data.Thyme.Clock.POSIX as TY
import qualified Data.Thyme.Time.Core as TY
import qualified Hedgehog.Gen as G
import qualified Hedgehog.Range as R
import qualified System.IO as IO
{-# ANN module ("HLint: ignore Redundant do" :: String) #-}
spec :: Spec
spec = describe "Arbor.File.Format.Asif.Write" $ do
it "should write file and read it back again" $ require $ property $ do
tplList <- forAll $ G.list (R.linear 0 50) genTriple
let fld = int64Segment (\(a,_,_) -> a) "first"
<> word16Segment (\(_,a,_) -> a) "second"
<> nullTerminatedStringSegment (\(_,_,a) -> T.toStrict a) "third"
(_, f, h) <- openBinaryTempFile Nothing "wtite-asif-test.asif"
_ <- writeAsif h "wxyz" Nothing fld tplList
liftIO $ IO.hSeek h IO.AbsoluteSeek 0
lbs <- liftIO $ LBS.hGetContents h
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, l1,l2,l3] <- forAll $ pure (segmentValues <$> segments)
((\(a,_,_) -> SInt64 a) <$> tplList) === l1
((\(_,a,_) -> SWord16 a) <$> tplList) === l2
((\(_,_,a) -> SString . T.encodeUtf8 $ a) <$> tplList) === l3
it "should write several segments, then read them back in" $ require $ property $ do
tplList <- forAll $ G.list (R.linear 0 50) genTriple
let f1 = int64Segment (\(a,_,_) -> a) "first"
let f2 = word16Segment (\(_,a,_) -> a) "second"
let f3 = nullTerminatedStringSegment (\(_,_,a) -> T.toStrict a) "third"
let fld = f1 <> f2 <> f3
lbs <- asifContent "wxyz" Nothing fld tplList
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, l1,l2,l3] <- forAll $ pure (segmentValues <$> segments)
((\(a,_,_) -> SInt64 a) <$> tplList) === l1
((\(_,a,_) -> SWord16 a) <$> tplList) === l2
((\(_,_,a) -> SString . T.encodeUtf8 $ a) <$> tplList) === l3
-----
it "should write out and read back in a bool segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) G.bool
lbs <- asifContent "wxyz" Nothing (boolSegment id "bool") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SBool <$> lst) === seg
it "should write out and read back in a word8 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (G.word8 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (word8Segment id "word8") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SWord8 <$> lst) === seg
it "should write out and read back in a word16 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (G.word16 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (word16Segment id "word16") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SWord16 <$> lst) === seg
it "should write out and read back in a word32 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (G.word32 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (word32Segment id "word32") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SWord32 <$> lst) === seg
it "should write out and read back in a word64 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (G.word64 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (word64Segment id "word64") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SWord64 <$> lst) === seg
-----
it "should write out and read back in a int8 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (G.int8 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (int8Segment id "int8") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SInt8 <$> lst) === seg
it "should write out and read back in a int16 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (G.int16 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (int16Segment id "int16") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SInt16 <$> lst) === seg
it "should write out and read back in a int32 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (G.int32 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (int32Segment id "int32") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SInt32 <$> lst) === seg
it "should write out and read back in a int64 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (G.int64 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (int64Segment id "int64") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SInt64 <$> lst) === seg
-----
it "should write out and read back in a ipv4 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (word32ToIpv4 <$> G.word32 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (ipv4Segment id "ipv4") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SIpv4 <$> lst) === seg
it "should write out and read back in a ipv6 segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) genIpv6
lbs <- asifContent "wxyz" Nothing (ipv6Segment id "ipv6") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SIpv6 <$> lst) === seg
-----
it "should write out and read back in a time segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) (TY.posixSecondsToUTCTime . TY.fromMicroseconds <$> G.int64 R.linearBounded)
lbs <- asifContent "wxyz" Nothing (utcTimeMicrosSegment id "time") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(STime <$> lst) === seg
-----
it "should write out and read back in a char segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) G.ascii
lbs <- asifContent "wxyz" Nothing (asciiSegment id "char") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SChar <$> lst) === seg
it "should write out and read back in a null-terminated string segment" $ require $ property $ do
lst <- forAll $ G.list (R.linear 0 50) genNonNullText
lbs <- asifContent "wxyz" Nothing (nullTerminatedStringSegment id "nullterminatedstring") (T.toStrict <$> lst)
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
(SString . T.encodeUtf8 <$> lst) === seg
it "should write out and read back in a text segment" $ require $ property $ do
-- Explaination of this test:
-- We can't deliniate 'breaks' in Text when we read it back in,
-- so regardless of how many we write out, when we read it back we only
-- get a single Text value.
lst <- forAll $ G.list (R.linear 1 50) (G.text (R.linear 0 50) G.unicode)
lbs <- asifContent "wxyz" Nothing (textSegment id "text") lst
let Right segments = extractSegments (AP.string "seg:wxyz") lbs
[names, times, types, seg] <- forAll $ pure (segmentValues <$> segments)
[SText . T.encodeUtf8 . T.concat $ T.fromStrict <$> lst] === seg
genTriple :: MonadGen m => m (Int64, Word16, T.Text)
genTriple
= (,,)
<$> G.int64 R.linearBounded
<*> G.word16 R.linearBounded
<*> genNonNullText
genIpv6 :: MonadGen m => m IP.IPv6
genIpv6 = word32x4ToIpv6 <$> gen4
where
gen4 :: MonadGen m => m (Word32, Word32, Word32, Word32)
gen4 =
let g = G.word32 R.linearBounded
in (,,,) <$> g <*> g <*> g <*> g
genNonNullText :: MonadGen m => m T.Text
genNonNullText =
T.fromStrict <$> G.text (R.linear 0 32) (G.filter (/= toEnum 0) G.unicode)
instance MonadResource m => MonadResource (PropertyT m) where
liftResourceT = lift . liftResourceT
-- I have no idea how this doesn't exist, but whatever. ¯\_(ツ)_/¯
instance MonadResource IO where
liftResourceT = runResourceT