hslua-1.3.0: test/Foreign/Lua/PeekTests.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-|
Module : Foreign.Lua.PeekTests
Copyright : © 2020 Albert Krewinkel
License : MIT
Maintainer : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
Stability : alpha
Portability : OverloadedStrings, TypeApplications
Tests for Haskell-value retriever functions.
-}
module Foreign.Lua.PeekTests (tests) where
import Control.Monad (forM_, zipWithM_)
import Data.Either (isLeft)
import Foreign.Lua.Peek
import Test.HsLua.Arbitrary ()
import Test.HsLua.Util ( (=:), pushLuaExpr, shouldBeResultOf
, shouldHoldForResultOf)
import Test.QuickCheck.Instances ()
import Test.QuickCheck.Monadic (monadicIO, run, assert)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (testProperty)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as Char8
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Foreign.Lua as Lua
import qualified Foreign.Lua.Utf8 as Utf8
-- | Calling Haskell functions from Lua.
tests :: TestTree
tests = testGroup "Peek"
[ testGroup "peekBool"
[ "True" =:
Right True `shouldBeResultOf` do
Lua.pushboolean True
peekBool Lua.stackTop
, "False" =:
Right False `shouldBeResultOf` do
Lua.pushboolean False
peekBool Lua.stackTop
, "Numbers are truthy" =:
Right True `shouldBeResultOf` do
Lua.pushnumber 0
peekBool Lua.stackTop
, "Nil is falsy" =:
Right False `shouldBeResultOf` do
Lua.pushnil
peekBool Lua.stackTop
-- no tests for failing cases, this function always succeeds.
]
, testGroup "peekIntegral"
[ "negative Int" =:
Right (-5) `shouldBeResultOf` do
Lua.pushinteger (-5)
peekIntegral @Int Lua.stackTop
, "Int as string" =:
Right 720 `shouldBeResultOf` do
Lua.pushstring "720"
peekIntegral @Int Lua.stackTop
, "fail on boolean" =:
let msg = "expected Integral, got 'true' (boolean)"
in Left (errorMsg msg) `shouldBeResultOf` do
Lua.pushboolean True
peekIntegral @Int Lua.stackTop
, "fail on non-numeric string" =:
let msg = "expected Integral, got 'not a number' (string)"
in Left (errorMsg msg) `shouldBeResultOf` do
Lua.pushstring "not a number"
peekIntegral @Integer Lua.stackTop
]
, testGroup "peekRealFloat"
[ "negative Float" =:
Right (-13.37) `shouldBeResultOf` do
Lua.pushnumber (-13.37)
peekRealFloat @Float Lua.stackTop
, "number as string" =:
Right (-720.0) `shouldBeResultOf` do
Lua.pushstring "-720"
peekRealFloat @Float Lua.stackTop
, "scientific notation string" =:
Right 0.00071 `shouldBeResultOf` do
Lua.pushstring "7.1e-4"
peekRealFloat @Float Lua.stackTop
, "fail on boolean" =:
let msg = "expected RealFloat, got 'true' (boolean)"
in Left (errorMsg msg) `shouldBeResultOf` do
Lua.pushboolean True
peekRealFloat @Float Lua.stackTop
, "fail on non-numeric string" =:
let msg = "expected RealFloat, got 'not a number' (string)"
in Left (errorMsg msg) `shouldBeResultOf` do
Lua.pushstring "not a number"
peekRealFloat @Double Lua.stackTop
]
, testGroup "Strings"
[ testGroup "peekByteString"
[ testProperty "retrieve any string" $ \bs -> monadicIO $ do
retrieved <- run $ Lua.run $ do
Lua.pushstring bs
peekByteString Lua.stackTop
assert (retrieved == Right bs)
, testProperty "retrieve integer as string" $ \n -> monadicIO $ do
retrieved <- run . Lua.run $ do
Lua.pushinteger n
peekByteString Lua.stackTop
let numberAsByteString = Char8.pack . show @Integer . fromIntegral $ n
assert (retrieved == Right numberAsByteString)
, "fails on boolean" =:
let msg = "expected string, got 'true' (boolean)"
in Left (errorMsg msg) `shouldBeResultOf` do
Lua.pushboolean True
peekByteString Lua.stackTop
]
, testGroup "peekText"
[ testProperty "retrieve any string" $ \bs -> monadicIO $ do
retrieved <- run $ Lua.run $ do
Lua.pushstring bs
peekText Lua.stackTop
assert (retrieved == Right (Utf8.toText bs))
, testProperty "retrieve UTF-8 encoded Text" $ \txt -> monadicIO $ do
retrieved <- run $ Lua.run $ do
Lua.pushstring (Utf8.fromText txt)
peekText Lua.stackTop
assert (retrieved == Right txt)
, testProperty "retrieve integer as Text" $ \n -> monadicIO $ do
retrieved <- run . Lua.run $ do
Lua.pushinteger n
peekText Lua.stackTop
let numberAsByteString = T.pack . show @Integer . fromIntegral $ n
assert (retrieved == Right numberAsByteString)
, "fails on nil" =:
let msg = "expected string, got 'nil' (nil)"
in Left (errorMsg msg) `shouldBeResultOf` do
Lua.pushnil
peekByteString Lua.stackTop
]
, testGroup "peekString"
[ testProperty "retrieve UTF-8 encoded string" $ \txt -> monadicIO $ do
retrieved <- run $ Lua.run $ do
Lua.pushstring (Utf8.fromString txt)
peekString Lua.stackTop
assert (retrieved == Right txt)
, "fails on table" =:
isLeft `shouldHoldForResultOf` do
_ <- Lua.pushglobaltable
peekString Lua.stackTop
, "fails on thread" =:
isLeft `shouldHoldForResultOf` do
_ <- Lua.pushthread
peekString Lua.stackTop
]
, testGroup "peekStringy"
[ testProperty "retrieve UTF-8 encoded string as Text" $ \txt ->
monadicIO $ do
retrieved <- run $ Lua.run $ do
Lua.pushstring (Utf8.fromText txt)
peekStringy @T.Text Lua.stackTop
assert (retrieved == Right txt)
, "retrieve ByteString" =:
Right "This is an ASCII string" `shouldBeResultOf` do
Lua.pushstring "This is an ASCII string"
peekStringy @B.ByteString Lua.stackTop
, "fails on table" =:
isLeft `shouldHoldForResultOf` do
_ <- Lua.pushglobaltable
peekStringy @B.ByteString Lua.stackTop
]
]
, testGroup "Containers"
[ testGroup "peekList"
[ "empty list" =:
Right [] `shouldBeResultOf` do
Lua.newtable
peekList peekBool Lua.stackTop
, testProperty "list of strings" $ \lst -> monadicIO $ do
retrieved <- run $ Lua.run $ do
Lua.newtable
zipWithM_
(\i s -> Lua.pushstring s *>
Lua.rawseti (Lua.nthFromTop 2) i)
[1..]
lst
peekList peekByteString Lua.stackTop
assert (retrieved == Right lst)
, "string keys are not in list" =:
Right [] `shouldBeResultOf` do
pushLuaExpr "{['1'] = 'hello', ['2'] = 'world'}"
peekList peekByteString Lua.stackTop
, "missing pair causes an error" =:
isLeft `shouldHoldForResultOf` do
pushLuaExpr "{[1] = 'hello', [2] = 'world', [4] = 'nope'}"
peekList peekByteString Lua.stackTop
]
, testGroup "peekSet"
[ "empty set" =:
Right Set.empty `shouldBeResultOf` do
Lua.newtable
peekSet peekBool Lua.stackTop
, testProperty "set of strings" $ \set -> monadicIO $ do
retrieved <- run $ Lua.run $ do
Lua.newtable
forM_ (Set.toList set) $ \x -> do
Lua.pushstring x
Lua.pushboolean True
Lua.rawset (Lua.nthFromTop 3)
peekSet peekByteString Lua.stackTop
assert (retrieved == Right set)
, "keys with falsy values are not in set" =:
Right (Set.fromList [1,3]) `shouldBeResultOf` do
pushLuaExpr "{['1'] = 'hello', ['2'] = false, [3] = 5}"
peekSet (peekIntegral @Int) Lua.stackTop
, "fails if element peeker fails" =:
let errorStack = [ "retrieving Set"
, "retrieving key-value pair"
, "retrieving key"
, "expected string, got 'true' (boolean)"]
in Left (PeekError $ NonEmpty.fromList errorStack) `shouldBeResultOf` do
pushLuaExpr "{ NaN = true, [true] = false }"
peekSet peekText Lua.stackTop
]
, testGroup "peekMap"
[ "empty map" =:
Right Map.empty `shouldBeResultOf` do
Lua.newtable
peekMap peekText peekText Lua.stackTop
, "tables become maps" =:
Right (Map.fromList [("one", 1), ("two", 2)]) `shouldBeResultOf` do
pushLuaExpr "{ one = 1, two = 2}"
peekMap peekText (peekIntegral @Int) Lua.stackTop
, "fails if key peeker fails" =:
let errorStack = [ "retrieving Map"
, "retrieving key-value pair"
, "retrieving key"
, "expected Integral, got 'NaN' (string)"]
in Left (PeekError $ NonEmpty.fromList errorStack) `shouldBeResultOf` do
pushLuaExpr "{ NaN = true }"
peekMap (peekIntegral @Int) peekBool Lua.stackTop
, "fails if value peeker fails" =:
let errorStack = [ "retrieving Map"
, "retrieving key-value pair"
, "retrieving value"
, "expected string, got 'true' (boolean)"]
in Left (PeekError $ NonEmpty.fromList errorStack) `shouldBeResultOf` do
pushLuaExpr "{ [42] = true }"
peekMap (peekIntegral @Int) peekText Lua.stackTop
]
]
, testGroup "combinators"
[ "optional with nil" =:
Right Nothing `shouldBeResultOf` do
Lua.pushnil
optional peekString Lua.top
, "optional with number" =:
Right (Just 23) `shouldBeResultOf` do
Lua.pushinteger 23
optional (peekIntegral @Int) Lua.top
]
]