{-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} module Foreign.JNISpec where import Control.Concurrent (runInBoundThread) import Control.Exception (try) import Data.List (sort) import Data.Singletons import Foreign.JNI.Internal (jniMethodToJavaSignature) import Foreign.JNI.String (fromChars) import Foreign.JNI.Types import Foreign.JNI.Unsafe import qualified Foreign.JNI.Unsafe.Internal as Internal import Foreign.JNI.Unsafe.Internal.Introspection import Test.Hspec spec :: Spec spec = do describe "runInAttachedThread" $ do it "can run jni calls in another thread" $ runInBoundThread $ runInAttachedThread $ do jclass <- findClass $ referenceTypeName (sing :: Sing ('Class "java.lang.Long")) deleteLocalRef jclass it "is needed to run jni calls in another thread" $ runInBoundThread $ do findClass (referenceTypeName (sing :: Sing ('Class "java.lang.Long"))) `shouldThrow` \ThreadNotAttached -> True around_ (runInBoundThread . runInAttachedThread) $ do describe "isInstanceOf" $ do it "identifies a class name as a String" $ do klong <- findClass (referenceTypeName (sing :: Sing ('Class "java.lang.Long"))) name <- callObjectMethod klong classGetNameMethod [] kstring <- findClass (referenceTypeName (sing :: Sing ('Class "java.lang.String"))) isInstanceOf name kstring `shouldReturn` True it "identifies a class name as an Object" $ do klong <- findClass (referenceTypeName (sing :: Sing ('Class "java.lang.Long"))) name <- callObjectMethod klong classGetNameMethod [] kobject <- findClass (referenceTypeName (sing :: Sing ('Class "java.lang.Object"))) isInstanceOf name kobject `shouldReturn` True it "doesn't identify a class name as a Long" $ do klong <- findClass (referenceTypeName (sing :: Sing ('Class "java.lang.Long"))) name <- callObjectMethod klong classGetNameMethod [] isInstanceOf name klong `shouldReturn` False describe "getMethodID" $ do it "gives correct hints on a mistake" $ do kstring <- findClass (referenceTypeName (sing :: Sing ('Class "java.lang.String"))) let sig = methodSignature [] (sing @('Class "java.lang.String")) result <- try $ getMethodID kstring (fromChars "replace") sig case result of Left e -> do sort (noSuchMethodOverloadings e) `shouldBe` [ "public java.lang.String java.lang.String.replace(char,char)" , "public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence)" ] _ -> expectationFailure "call should have failed with a NoSuchMethod exception" describe "jniMethodToJavaSignature" $ it "converts JNI signatures correctly" $ do jniMethodToJavaSignature "()J" `shouldBe` Right ([], "long") jniMethodToJavaSignature "(I)B" `shouldBe` Right (["int"], "byte") jniMethodToJavaSignature "(SF)V" `shouldBe` Right (["short", "float"], "void") jniMethodToJavaSignature "(C[Ljava/lang/String;D)Z" `shouldBe` Right (["char", "java.lang.String[]", "double"], "boolean") describe "showException" $ it "correctly displays exceptions" $ do kinteger <- findClass (referenceTypeName (sing :: Sing ('Class "java.lang.Integer"))) let sig = methodSignature [] (sing :: Sing 'Void) result <- try $ Internal.getMethodID kinteger (fromChars "toString") sig case result of Left (e :: JVMException) -> showException e `shouldReturn` "java.lang.NoSuchMethodError: toString\n" _ -> expectationFailure "call should have failed with a JVMException"