jvm-binary-0.6.1: test/Language/JVMSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
module Language.JVMSpec where
import SpecHelper
import Test.Hspec hiding (shouldBe, shouldSatisfy, shouldMatchList)
import qualified Data.ByteString.Lazy as BL
import Data.Either
import Data.Foldable
import Data.List as List
import qualified Data.Text as Text
-- prelude
import System.Environment
-- filepath
import System.FilePath
-- vector
import qualified Data.Vector as V
-- zip-archive
import Codec.Archive.Zip
import Language.JVM
import qualified Language.JVM.Attribute.Code as C
import Language.JVM.Attribute.StackMapTable
spec :: Spec
spec = do
describe "Main.class" $ do
fl <- runIO $ BL.readFile "test/data/project/Main.class"
it "can read classfile from file" $ do
case readClassFile fl of
Right clf -> do
cThisClass clf `shouldBe` "Main"
cSuperClass clf `shouldBe` "java/lang/Object"
Left msg ->
fail $ show msg
it "can decode a classfile from file" $ do
case decodeClassFile fl of
Right _ ->
True `shouldBe` True
Left msg ->
fail $ show msg
spec_reading_classfile
describe "the standard library" $ do
runIO (lookupEnv "JAVA_HOME") >>= \case
Just home -> do
Right archive <- runIO $ readZipFile (home </> "jre/lib/rt.jar")
let priorities =
[ "java/lang/Class.class"
]
forM_ priorities $ \priority -> do
let Just entry = findEntryByPath priority archive
it ("can read priority " ++ priority) $ do
case readClassFile (fromEntry entry) of
Right _ ->
True `shouldBe` True
Left msg ->
fail $ show msg
let classes =
filter (\entry -> takeExtension (eRelativePath entry) == ".class")
(zEntries archive)
forM_ classes $ \entry -> do
it ("can read " ++ eRelativePath entry) $ do
case readClassFile (fromEntry entry) of
Right _ ->
True `shouldBe` True
Left msg ->
fail $ show msg
forM_ classes $ \entry -> do
it ("can read " ++ eRelativePath entry) $ do
case readClassFile (fromEntry entry) of
Right _ ->
True `shouldBe` True
Left msg ->
fail $ show msg
Nothing -> do
runIO $ putStrLn "Expecting JAVA_HOME to be set"
spec_reading_classfile :: Spec
spec_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 (EvolveConfig [] cp' (const True)) (evolve m)) of
Right _ -> return ()
Left err -> do
putStr (show err) >> putStr ": "
print . runEvolve (EvolveConfig [] cp' (const True)) $ 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_ (byteCodeInstructions . C.codeByteCode $ c) $ \i ->
putStr " -> " >> print i
forM_ (C.codeAttributes c) $ \ca -> do
print $ runEvolve (EvolveConfig [] cp' (const True)) (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 (const True) 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
(poolCount $ cConstantPool d') `shouldSatisfy`
(<= (poolCount $ 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 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 :: Code Low -> V.Vector (ByteCodeInst Low)) 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)
readZipFile :: FilePath -> IO (Either String Archive)
readZipFile =
fmap toArchiveOrFail . BL.readFile