packages feed

xch-0.1.0.1: test/Main.hs

module Main (main) where

import qualified Data.ByteString.Lazy.Char8 as BS
import Data.List
import Data.Word
import Data.Xch
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Paths_xch
import System.FilePath
import Text.Megaparsec
import Test.Tasty
import Test.Tasty.Golden
import Test.Tasty.HUnit
import Test.Tasty.Hedgehog

main :: IO ()
main = getDataDir >>= \dataDir ->
  defaultMain $ testGroup "Test.Xch"
  [ testEmpty, testSVDat, testMsnEvt, testComStat, testAEDat, ptrip
  , goldTest "sample" (dataDir </> "sample.golden")
    [ SVDat "foo" [Time 34.5, Pos $ NED 0 1 2, Vel $ NED 0.3 0.4 0.5]
    , SVDat "bar" [Time 2.2, Pos $ NED 0.5 0.6 0.7]
    , MsnEvt "at noon" ["lunch", "hot dog", "wine"]
    , MsnEvt "at 6PM"  ["dinner", "spaghetti", "boba"]
    , ComStat Good
    , AEDat "anomaly"
      [ ActDurTime 4 5
      , LocGeo "area 61" [(Cord 0 1 2, Cord 3 4 5.9), (Cord 5 6 7.8, Cord 9 10 11.1)] (Just 3.14)
      , Dfn "cactus"
      ]
    , AEDat "emergency" [Dfn "fashion"]
    ]
  ]

testEmpty :: TestTree
testEmpty = testCase "empty" $ do
  ptest "<xch/>" []
  ptest "<xch></xch>" []

testSVDat :: TestTree
testSVDat = testCaseSteps "SVDat" $ \step -> do
  step "state"
  ptest "<xch><svdat><state>foo</state></svdat></xch>" [SVDat "foo" []]
  ptest "<xch><svdat><state>foo bar</state></svdat></xch>" [SVDat "foo bar" []]
  ptest "<xch><svdat><state><escape</state></svdat></xch>" [SVDat "<escape" []]
  ptest "<xch><svdat><state>\n\t</state></svdat></xch>" [SVDat "\n\t" []]
  step "time"
  ptest "<xch><svdat><state>foo</state><time>2026-11-14T04:58:32.583506318Z</time></svdat></xch>"
    [SVDat "foo" [Time 1794632312.583506318]]
  ptest "<xch><svdat><state></state><time>2026-11-14T04:58:32Z</time></svdat></xch>"
    [SVDat "" [Time 1794632312]]
  step "pos"
  ptest "<xch><svdat><state>foo</state><pos><ned>\
    \<north>3</north><east>-4</east><down>5</down>\
    \</ned></pos></svdat></xch>"
      [SVDat "foo" [Pos $ NED 3.0 (-4.0) 5.0]]
  ptest "<xch><svdat><state>foo</state><pos><ned>\
    \<north>3.1</north><east>-4.2</east><down>5.5</down>\
    \</ned></pos></svdat></xch>"
      [SVDat "foo" [Pos $ NED 3.1 (-4.2) 5.5]]
  step "vel"
  ptest "<xch><svdat><state>foo</state><vel><ned>\
    \<north>+3</north><east>4</east><down>5.2</down>\
    \</ned></vel></svdat></xch>"
      [SVDat "foo" [Vel $ NED 3.0 4.0 5.2]]
  ptest "<xch><svdat><state>foo</state><vel><ned>\
    \<north>3.1e5</north><east>- 4.2</east><down>5.5</down>\
    \</ned></vel></svdat></xch>"
      [SVDat "foo" [Vel $ NED 3.1e5 (-4.2) 5.5]]

testMsnEvt :: TestTree
testMsnEvt = testCase "MsnEvt" $ do
  ptest "<xch><misn><trig>foo</trig></misn></xch>" [MsnEvt "foo" []]
  ptest "<xch><misn><trig>bar</trig><evt>up</evt></misn></xch>" [MsnEvt "bar" ["up"]]
  ptest "<xch><misn><trig>baz</trig><evt>left</evt><evt>right</evt></misn></xch>"
    [MsnEvt "baz" ["left", "right"]]

testComStat :: TestTree
testComStat = testCase "ComStat" $ do
  ptest "<xch><com><excellent/></com></xch>" [ComStat Excellent]
  ptest "<xch><com><good/></com></xch>" [ComStat Good]
  ptest "<xch><com><bad/></com></xch>" [ComStat Bad]
  ptest "<xch><com><lost/></com></xch>" [ComStat Lost]

testAEDat :: TestTree
testAEDat = testCase "AEDat" $ do
  ptest "<xch><aedat><ae>foo</ae></aedat></xch>" [AEDat "foo" []]
  ptest "<xch><aedat><ae>bar</ae><actdur><act>4</act><dur>5</dur></actdur></aedat></xch>"
    [AEDat "bar" [ActDurTime 4 5]]
  ptest "<xch><aedat><ae>baz</ae><dfn>zoo</dfn></aedat></xch>"
    [AEDat "baz" [Dfn "zoo"]]
  ptest "<xch><aedat><ae>bar</ae>\
    \<loc><geo>San Diego</geo>\
    \  <lat><cord><dgs>34</dgs><mns>-2</mns><sds>3.14</sds></cord></lat>\
    \  <lon><cord><dgs>0</dgs><mns>55</mns><sds>1.0001</sds></cord></lon>\
    \  <alt>45</alt>\
    \</loc></aedat></xch>"
    [AEDat "bar" [LocGeo "San Diego" [(Cord 34 (-2) 3.14, Cord 0 55 1.0001)] $ Just 45]]

ptest :: String -> Xch -> Assertion
ptest i expt = case parse pxch "" i of
  Left  err -> assertFailure $ errorBundlePretty err
  Right act -> act @?= expt

ptrip :: TestTree
ptrip = testProperty "Trip" $ property $ do
  x <- forAll gxch
  tripping x wxch (parse pxch "")

gxch :: MonadGen m => m Xch
gxch = glist gxdat

gxdat :: MonadGen m => m XDat
gxdat = Gen.choice
  [ SVDat <$> gstr "state" <*> glist gsvdat
  , MsnEvt <$> gstr "trig" <*> glist (gstr "evt")
  , ComStat <$> Gen.element [Excellent, Good, Bad, Lost]
  , AEDat <$> gstr "ae" <*> glist gaeinf
  ]

gsvdat :: MonadGen m => m SVDat
gsvdat = Gen.choice
  [ Time <$> Gen.realFrac_ (Range.linearFracFrom 0 0 $ fromIntegral (maxBound :: Word32))
  , Pos <$> gned
  , Vel <$> gned
  ]

gaeinf :: MonadGen m => m AEInfo
gaeinf = Gen.choice
  [ ActDurTime <$> gint <*> gint
  , LocGeo <$> gstr "geo" <*> glist ((,) <$> gcord <*> gcord) <*> Gen.maybe gdbl
  , Dfn <$> gstr "dfn"
  ]

gcord :: MonadGen m => m Cord
gcord = Cord <$> gint <*> gint <*> gdbl

gned :: MonadGen m => m NED
gned = NED <$> gdbl <*> gdbl <*> gdbl

gint :: MonadGen m => m Integer
gint = Gen.integral_ $ Range.linearFrom 0 (-999999999999999) 999999999999999

gdbl :: MonadGen m => m Double
gdbl = Gen.double $ Range.linearFracFrom 0 (-999999999999999) 999999999999999

gstr :: MonadGen m => String -> m String
gstr t = do
  str <- Gen.string rangeList Gen.unicode
  if concat ["<", t, "/>"] `isInfixOf` str
    then Gen.discard
    else return str

glist :: MonadGen m => m a -> m [a]
glist = Gen.list rangeList

rangeList :: Range Int
rangeList = Range.linearFrom 0 0 64

goldTest :: String -> FilePath -> Xch -> TestTree
goldTest n p x = goldenVsString n p $ return $ BS.pack $ wxch x ++ "\n"