1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
|
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-|
Module : HsLua.Packaging.Module
Copyright : © 2019-2024 Albert Krewinkel
License : MIT
Maintainer : Albert Krewinkel <tarleb@hslua.org>
Stability : alpha
Portability : Requires GHC 8 or later.
Tests for HsLua.
-}
module HsLuaTests (tests) where
import Prelude hiding (concat)
import Control.Monad (void)
import Data.ByteString (append)
import Data.Data (Typeable)
import Data.Either (isLeft)
import HsLua as Lua
import System.Mem (performMajorGC)
import Test.Tasty.HsLua ( (=:), (?:), pushLuaExpr, shouldBeErrorMessageOf
, shouldHoldForResultOf)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit ((@?=), assertBool, assertEqual, testCase)
import qualified Control.Monad.Catch as Catch
import qualified Data.ByteString.Char8 as Char8
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified HsLua.Core.Utf8 as Utf8
-- | Specifications for Attributes parsing functions.
tests :: TestTree
tests = testGroup "Lua integration tests"
[ testCase "print version" .
run $ do
openlibs
void $ getglobal @Lua.Exception "assert"
pushstring "Hello from "
void $ getglobal @Lua.Exception "_VERSION"
concat 2
call 1 0
, "getting a nested global works" ?: do
pushLuaExpr @Lua.Exception "{greeting = 'Moin'}"
setglobal "hamburg"
getglobal' "hamburg.greeting"
pushLuaExpr "'Moin'"
equal (-1) (-2)
, "setting a nested global works" ?: do
let v = "Mitte"
newtable
setglobal @Lua.Exception "berlin"
pushstring v
setglobal' "berlin.neighborhood"
v' <- getglobal' "berlin.neighborhood" *> tostring (-1)
return (Just v == v')
, testCase "table reading" .
run @Lua.Exception $ do
openbase
let tableStr = "{firstname = 'Jane', surname = 'Doe'}"
pushLuaExpr $ "setmetatable(" `append` tableStr `append` ", {'yup'})"
void $ getfield top "firstname"
firstname <- tostring top <* pop 1
liftIO (assertEqual "Wrong value for firstname" (Just "Jane") firstname)
pushstring "surname"
rawget (-2)
surname <- tostring top <* pop 1
liftIO (assertEqual "Wrong value for surname" surname (Just "Doe"))
hasMetaTable <- getmetatable (-1)
liftIO (assertBool "getmetatable returned wrong result" hasMetaTable)
rawgeti (-1) 1
mt1 <- tostring top <* pop 1
liftIO (assertEqual "Metatable content not as expected " mt1 (Just "yup"))
, testGroup "Getting strings to and from the stack"
[ testCase "unicode ByteString" $ do
let val = T.pack "öçşiğüİĞı"
val' <- run $ do
pushstring (T.encodeUtf8 val)
fmap T.decodeUtf8 `fmap` tostring 1
assertEqual "Popped a different value or pop failed" (Just val) val'
, testCase "ByteString should survive after GC/Lua destroyed" $ do
(val, val') <- run $ do
let v = "ByteString should survive"
pushstring v
v' <- tostring 1
pop 1
return (Just v, v')
performMajorGC
assertEqual "Popped a different value or pop failed" val val'
, testCase "String with NUL byte should be pushed/popped correctly" $ do
let str = "A\NULB"
str' <- run $ pushstring (Char8.pack str) *> tostring 1
assertEqual "Popped string is different than what's pushed"
(Just str) (Char8.unpack <$> str')
]
, testGroup "luaopen_* functions" $ map (uncurry testOpen)
[ ("base", openbase)
, ("debug", opendebug)
, ("io", openio)
, ("math", openmath)
, ("os", openos)
, ("package", openpackage)
, ("string", openstring)
, ("table", opentable)
]
, testGroup "error handling"
[ "catching error of a failing meta method" =:
isLeft `shouldHoldForResultOf`
let comp = do
pushLuaExpr "setmetatable({}, {__index = error})"
void $ getfield (-1) "foo"
in try comp
, "calling a function that errors throws exception" =:
"[string \"return error('error message')\"]:1: error message"
`shouldBeErrorMessageOf` do
openbase
loadstring "return error('error message')" *> call 0 1
, let errTbl ="setmetatable({}, {__index = function(t, k) error(k) end})"
in testGroup "error conversion"
[ "throw custom exceptions" =: do
let comp = do
openlibs
pushLuaExpr errTbl
pushnumber 23
void $ gettable (Lua.nth 2)
result <- tryCustom comp
result @?= Left (ExceptionWithNumber 23)
, "catch custom exception in exposed function" =: do
let frob = do
openlibs
pushLuaExpr errTbl
pushnumber 42
_ <- gettable (Lua.nth 2)
return (NumResults 1)
result <- tryCustom $ do
openlibs
pushHaskellFunction frob
call (NumArgs 0) (NumResults 1)
result @?= Left (ExceptionWithNumber 42)
, "pass exception through Lua" =: do
let frob :: LuaE CustomException NumResults
frob = Catch.throwM (ExceptionWithMessage "borked")
result <- tryCustom $ do
pushHaskellFunction frob
call (NumArgs 0) (NumResults 0)
result @?= Left (ExceptionWithMessage "borked")
]
]
]
-------------------------------------------------------------------------------
-- luaopen_* functions
testOpen :: String -> Lua () -> TestTree
testOpen lib openfn = testCase ("open" ++ lib) $
assertBool "opening the library failed" =<<
run (openfn *> istable (-1))
-------------------------------------------------------------------------------
-- Custom exception handling
data CustomException =
ExceptionWithNumber Lua.Number
| ExceptionWithMessage String
deriving (Eq, Show, Typeable)
instance Catch.Exception CustomException
instance LuaError CustomException where
pushException = \case
ExceptionWithMessage m -> pushstring (Utf8.fromString m)
ExceptionWithNumber n -> pushnumber n
popException = do
Lua.tonumber Lua.top >>= \case
Just num -> do
Lua.pop 1
return (ExceptionWithNumber num)
_ -> do
l <- Lua.state
msg <- Lua.liftIO (Lua.popErrorMessage l)
return (ExceptionWithMessage (Utf8.toString msg))
luaException = ExceptionWithMessage
tryCustom :: LuaE CustomException a -> IO (Either CustomException a)
tryCustom = Catch.try . Lua.run
-- instance Lua
-- customAlternative :: Lua a -> Lua a -> Lua a
-- customAlternative x y = Catch.try x >>= \case
-- Left (_ :: CustomException) -> y
-- Right x' -> return x'
|