jvm-binary-0.1.0: test/Language/JVMTest.hs
{-# LANGUAGE OverloadedStrings #-}
module Language.JVMTest where
import SpecHelper
import Data.Either
import qualified Data.IntMap as IM
import Data.List as List
import qualified Data.Text as Text
import qualified Data.ByteString.Lazy as BL
import Data.Foldable
import Language.JVM
import qualified Language.JVM.Attribute.Code as C
import Language.JVM.Attribute.StackMapTable
spec_testing_example :: SpecWith ()
spec_testing_example =
it "can read classfile from file" $ do
eclf <- readClassFile <$> BL.readFile "test/data/project/Main.class"
case eclf of
Right clf -> do
cThisClass clf `shouldBe` ClassName "Main"
cSuperClass clf `shouldBe` ClassName "java/lang/Object"
Left msg ->
fail $ show msg
test_reading_classfile :: IO [TestTree]
test_reading_classfile = testAllFiles $ \bs -> do
let d = decodeClassFile bs
it "can parse the bytestring" $ do
d `shouldSatisfy` isRight
let Right cls = d
it "has a the magic number: 0xCAFEBABE" $ do
cMagicNumber cls `shouldBe` 0xCAFEBABE
it "can bootstrap the constant pool" $ do
let
cp = bootstrapConstantPool (cConstantPool cls)
cp `shouldSatisfy` isRight
let Right cp' = cp
forM_ (cMethods' cls) $ \m -> do
case (runEvolve cp' (evolve m)) of
Right _ -> return ()
Left err -> do
putStr (show err) >> putStr ": "
print . runEvolve cp' $ do
x <- link (mDescriptor m)
n <- link (mName m)
return ((n, x) :: (MethodDescriptor, Text.Text))
forM_ (mAttributes m) $ \a -> do
-- Assume code
case fromAttribute' a :: Either String (C.Code Low) of
Right c -> do
forM_ (unByteCode . C.codeByteCode $ c) $ \i ->
putStr " -> " >> print i
forM_ (C.codeAttributes c) $ \ca -> do
print $ runEvolve cp' (evolve ca)
putStrLn (hexStringS $ aInfo ca)
case fromAttribute' ca :: Either String (StackMapTable Low) of
Right x ->
print x
Left msg ->
print msg
Left x ->
print x
describe "encoding/decoding" $ do
let e = encodeClassFile cls
it "should encode to the original bytestring" $
e `shouldBe` bs
it "should decode to the same thing" $
decodeClassFile e `shouldBe` Right cls
describe "evolving/devolving" $ do
let me = evolveClassFile cls
it "can evolve the whole class file" $ do
me `shouldSatisfy` isRight
let Right x = me
it "has same or smaller constant pool" $ do
let d' = devolveClassFile x
(IM.size . unConstantPool $ cConstantPool d') `shouldSatisfy`
(<= (IM.size . unConstantPool $ cConstantPool cls))
-- it "is the same when devolving with the original constant pool" $
-- devolveClassFile' (cConstantPool cls) x `shouldMatchClass'` cls
it "can do full read - write - read process" $ do
let w = writeClassFile' (cConstantPool cls) x
let y' = readClassFile w
y' `shouldSatisfy` isRight
let Right y = y'
x `shouldMatchClass` y
shouldMatchClass :: ClassFile High -> ClassFile High -> IO ()
shouldMatchClass y x = do
cAccessFlags' y `shouldBe` cAccessFlags' x
cThisClass y `shouldBe` cThisClass x
cSuperClass y `shouldBe` cSuperClass x
cInterfaces y `shouldBe` cInterfaces x
cFields' y `shouldBe` cFields' x
forM_ (zip (cMethods y) (cMethods x)) $ \ (ym, xm) -> do
ym `shouldMatchMethod` xm
y `shouldBe` x
shouldMatchClass' :: ClassFile Low -> ClassFile Low -> IO ()
shouldMatchClass' y x = do
cAccessFlags' y `shouldBe` cAccessFlags' x
cThisClass y `shouldBe` cThisClass x
cSuperClass y `shouldBe` cSuperClass x
cInterfaces y `shouldBe` cInterfaces x
cFields' y `shouldBe` cFields' x
forM_ (zip (cMethods y) (cMethods x)) $ \(ym, xm) -> do
shouldMatchMethod' ym xm
shouldMatchMethod :: Method High -> Method High -> IO ()
shouldMatchMethod ym xm = do
mAccessFlags ym `shouldBe` mAccessFlags xm
mName ym `shouldBe` mName xm
mDescriptor ym `shouldBe` mDescriptor xm
mExceptions ym `shouldMatchList` mExceptions xm
case (mCode ym, mCode xm) of
(Just yc, Just xc) -> do
cmpOver C.codeByteCodeOprs yc xc $ \ yb xb -> do
yb `shouldBe` xb
_ -> mCode ym `shouldBe` mCode xm
shouldMatchMethod' :: Method Low -> Method Low -> IO ()
shouldMatchMethod' ym xm = do
mAccessFlags ym `shouldBe` mAccessFlags xm
mName ym `shouldBe` mName xm
mDescriptor ym `shouldBe` mDescriptor xm
forM_ (zip (unSizedList $ mAttributes ym ) (unSizedList $ mAttributes xm)) $ \(ya, xa) -> do
case (fromAttribute' ya, fromAttribute' xa) of
(Right yc, Right xc) -> do
cmpOn C.codeMaxStack yc xc
cmpOn C.codeMaxLocals yc xc
cmpOn C.codeByteCodeInsts yc xc
cmpOn C.codeExceptionTable yc xc
cmpOver (List.sort . unSizedList .C.codeAttributes) yc xc $ \ yca xca -> do
case (fromAttribute' yca, fromAttribute' xca)
:: (Either String (StackMapTable Low), Either String (StackMapTable Low)) of
(Right yst, Right xst) -> do
cmpOver stackMapTable yst xst $ shouldBe
(yst, xst) ->
yst `shouldBe` xst
(yc, xc) ->
yc `shouldBe` xc
cmpOn :: (Show b, Eq b) => (a -> b) -> a -> a -> IO ()
cmpOn f a b =
f a `shouldBe` f b
cmpOver :: (Foldable t) => (a -> t b) -> a -> a -> (b -> b -> IO ()) -> IO ()
cmpOver g ta tb f =
forM_ (zip (toList . g $ ta) (toList . g $ tb)) (uncurry f)
cmpPrefixes :: (Foldable t) => (a -> t b) -> a -> a -> ([b] -> [b] -> IO ()) -> IO ()
cmpPrefixes g ta tb f =
forM_ (zip (inits . toList . g $ ta) (inits . toList . g $ tb)) (uncurry f)