{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Control.Monad
import Data.Binary (encode)
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Int
import qualified Data.Text as T
import qualified Data.Vector.Storable as SVec
import Data.Word
import Futhark.Data
import Futhark.Data.Compare
import Futhark.Data.Parser
import Futhark.Data.Reader
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Text.Megaparsec
import Text.Megaparsec.Char (space)
-- A hack to get around the default Eq instance for values, which does
-- not handle NaNs the way we need.
newtype TestValue = TestValue {unTestValue :: Value}
instance Eq TestValue where
TestValue x == TestValue y =
null $ compareValues (Tolerance 0) x y
instance Show TestValue where
show (TestValue x) = show x
instance Arbitrary TestValue where
arbitrary =
TestValue
<$> oneof
[ mk I8Value,
mk I16Value,
mk I32Value,
mk I64Value,
mk U8Value,
mk U16Value,
mk U32Value,
mk U64Value,
mk F32Value,
mk F64Value,
mk BoolValue
]
where
mk f = do
-- Careful not to create enormous arrays.
shape <- listOf $ choose (0, 3)
f (SVec.fromList shape) . SVec.fromList <$> replicateM (product shape) arbitrary
scalar :: (SVec.Storable a) => (Vector Int -> Vector a -> Value) -> a -> Value
scalar f x = f mempty (SVec.singleton x)
readerTests :: TestTree
readerTests =
testGroup
"Reader"
[ test "1" [scalar I32Value 1],
test "2i32" [scalar I32Value 2],
test "3i64" [scalar I64Value 3],
test "[1, 2, 3]" [I32Value (SVec.fromList [3]) (SVec.fromList [1, 2, 3])],
test
"2i32 [1, 2, 3]"
[ scalar I32Value 2,
I32Value (SVec.fromList [3]) (SVec.fromList [1, 2, 3])
],
test
"[[1,-- comment\n 2], [3,4]]"
[I32Value (SVec.fromList [2, 2]) (SVec.fromList [1, 2, 3, 4])],
test
"b\STX\SOH i32\SOH\NUL\NUL\NUL\NUL\NUL\NUL\NUL\154\238\179u"
[I32Value (SVec.fromList [1]) (SVec.fromList [1974726298])],
test
"0.9597767951851629f64\nb\STX\SOH i32\SOH\NUL\NUL\NUL\NUL\NUL\NUL\NUL\DC1\ETX\NUL\NUL2"
[ scalar F64Value 0.9597767951851629,
I32Value (SVec.fromList [1]) (SVec.fromList [785]),
scalar I32Value 2
],
testProperty "read random binary data" $
\v ->
(map TestValue <$> readValues (encode $ unTestValue v))
== Just [v],
testProperty "read random text data" $
\v ->
(map TestValue <$> readValues (LBS.pack $ T.unpack $ valueText $ unTestValue v))
== Just [v]
]
where
maxlen = 40
shorten s
| length s < maxlen = s
| otherwise = take (maxlen - 3) s <> "..."
test s x =
testCase ("Reading " <> shorten (show s)) $
readValues s @?= Just x
parserTests :: TestTree
parserTests =
testGroup
"Parser"
[ test "1" $ scalar I32Value 1,
negtest "_1",
test "2i32" $ scalar I32Value 2,
test "3i64" $ scalar I64Value 3,
test "-2_3i32" $ scalar I32Value (-23),
test "0b1_0_0_1" $ scalar I32Value 9,
test "0x12_34" $ scalar I32Value 0x1234,
test "3.1_4" $ scalar F64Value 3.14,
test "1.0" $ scalar F64Value 1,
negtest "_1.0",
test "2f32" $ scalar F32Value 2,
test "2f16" $ scalar F16Value 2,
test "3.0f64" $ scalar F64Value 3.0,
test "3.1f64" $ scalar F64Value 3.1,
test "3.1_e-2f64" $ scalar F64Value 3.1e-2,
test "f32.nan" $ scalar F32Value (0 / 0),
test "f16.nan" $ scalar F16Value (0 / 0),
test "f64.nan" $ scalar F64Value (0 / 0),
test "f64.inf" $ scalar F64Value (1 / 0),
test "-f64.inf" $ scalar F64Value (-1 / 0),
test "true" $ scalar BoolValue True,
test "false" $ scalar BoolValue False,
test "\"foo\"" $ putValue1 ("foo" :: T.Text),
test "\"\\\"foo\\\"\"" $ putValue1 ("\"foo\"" :: T.Text),
negtest "tr_ue",
test "[1,2,3,]" $ I32Value (SVec.fromList [3]) (SVec.fromList [1, 2, 3]),
negtest "[1i32, 2i8]",
test "[1i8, 0]" $ I8Value (SVec.fromList [2]) (SVec.fromList [1, 0]),
negtest "[0, 1i8]",
testProperty "parse random data" $
\v ->
(TestValue <$> parseMaybe (parseValue space) (valueText $ unTestValue v))
== Just v
]
where
test s x =
testCase ("Parsing " <> show s) $
(TestValue <$> runParser (parseValue space <* eof) "" s) @?= Right (TestValue x)
negtest s =
testCase ("Parsing " <> show s) $
either
(const Nothing)
Just
(TestValue <$> runParser (parseValue space <* eof) "" s)
@?= Nothing
getValueTests :: TestTree
getValueTests =
testGroup
"GetValue"
[ test (putValue1 (1 :: Int32)) (Nothing :: Maybe [Word8]),
test (putValue1 (1 :: Int32)) (Just (1 :: Int32)),
test (putValue1 (1 :: Int32)) (Just (1 :: Integer)),
test (putValue1 (42 :: Int64)) (Just (42 :: Int))
]
where
test v expected =
testCase (unwords ["getValue", v', "==", show expected]) $
getValue v @?= expected
where
v' = T.unpack (valueText v)
allTests :: TestTree
allTests =
testGroup "" [readerTests, parserTests, getValueTests]
main :: IO ()
main = defaultMain allTests