melf-1.3.0: tests/Golden.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Main (main) where
-- import Paths_elf
import Prelude as P
import Control.Monad
import Control.Monad.Catch
import Data.Binary
import qualified Data.ByteString as BS
import Data.ByteString.Lazy as BSL
-- import Data.ByteString.Lazy.Char8 as BSC
import Data.Foldable as F
-- import Data.Functor.Identity
import Data.Int
import GHC.IO.Encoding (setLocaleEncoding)
import Prettyprinter
import Prettyprinter.Render.Text
import System.Directory
import System.FilePath
import System.IO as IO
import Test.Tasty
import Test.Tasty.Golden
import Test.Tasty.HUnit
import Control.Exception.ChainedException
import Data.Elf
import Data.Elf.PrettyPrint
import qualified Data.Elf.Headers as H
import Data.Elf.Headers hiding (Header)
import Data.Endian
partitionM :: Monad m => (a -> m Bool) -> [a] -> m ([a], [a])
partitionM p = foldlM f ([], [])
where
f (ts, fs) x = do
b <- p x
return $ if b then (x:ts, fs) else (ts, x:fs)
traverseDir :: FilePath -> (FilePath -> IO Bool) -> IO [FilePath]
traverseDir root ok = go root
where
go :: FilePath -> IO [FilePath]
go dir = do
paths <- P.map (dir </>) <$> listDirectory dir
(dirPaths, filePaths) <- partitionM doesDirectoryExist paths
oks <- filterM ok filePaths
(oks ++) <$> (F.concat <$> mapM go dirPaths)
isElf :: FilePath -> IO Bool
isElf p = if takeExtension p == ".bad" then return False else (elfMagic ==) . decode <$> BSL.readFile p
decodeOrFailAssertion :: Binary a => ByteString -> IO (Int64, a)
decodeOrFailAssertion bs = case decodeOrFail bs of
Left (_, off, err) -> assertFailure (err ++ " @" ++ show off)
Right (_, off, a) -> return (off, a)
mkTest'' :: forall a . SingElfClassI a => HeaderXX a -> ByteString -> Assertion
mkTest'' HeaderXX{..} bs = do
let
takeLen off len = BSL.take (fromIntegral len) $ BSL.drop (fromIntegral off) bs
bsSections = takeLen hShOff (hShEntSize * hShNum)
bsSegments = takeLen hPhOff (hPhEntSize * hPhNum)
(ss :: [SectionXX a]) <- parseBList hData bsSections
assertEqual "Section table round trip does not work" bsSections $ serializeBList hData ss
(ps :: [SegmentXX a]) <- parseBList hData bsSegments
assertEqual "Segment table round trip does not work" bsSegments $ serializeBList hData ps
mkTest' :: ByteString -> Assertion
mkTest' bs = do
(off, elfh@(H.Header classS hxx)) <- decodeOrFailAssertion bs
assertBool "Incorrect header size" (headerSize (fromSingElfClass classS) == off)
assertEqual "Header round trip does not work" (BSL.take off bs) (encode elfh)
withSingElfClassI classS mkTest'' hxx bs
mkTest :: FilePath -> TestTree
mkTest p = testCase p $ withBinaryFile p ReadMode (BSL.hGetContents >=> mkTest')
mkGoldenTest' :: FilePath -> FilePath -> (FilePath -> IO (Doc ())) -> FilePath -> TestTree
mkGoldenTest' g o formatFunction file = goldenVsFile file g o mkGoldenTestOutput
where
mkGoldenTestOutput :: IO ()
mkGoldenTestOutput = do
doc <- formatFunction file
withFile o WriteMode (`hPutDoc` doc)
mkGoldenTest :: String -> (FilePath -> IO (Doc ())) -> FilePath -> TestTree
mkGoldenTest name formatFunction file = mkGoldenTest' g o formatFunction file
where
newBase = "tests" </> file <.> name
o = newBase <.> "out"
g = newBase <.> "golden"
mkGoldenTestOSuffix :: String -> String -> (FilePath -> IO (Doc ())) -> FilePath -> TestTree
mkGoldenTestOSuffix name osuffix formatFunction file = mkGoldenTest' g o formatFunction file
where
newBase = "tests" </> file <.> name
o = newBase <.> osuffix <.> "out"
g = newBase <.> "golden"
------------------------------------------------------------------------------
-- FIXME: define foldMapRBuilderList
index' :: (Integral i, MonadThrow m) => [a] -> i -> m a
index' (x:_) 0 = return x
index' (_:xs) n | n > 0 = index' xs (n-1)
| otherwise = $chainedError "index': negative argument."
index' _ _ = $chainedError "index': index too large."
getStringTable :: MonadThrow m => Headers -> BSL.ByteString -> m BSL.ByteString
getStringTable (Headers classS HeaderXX{..} ss _) bs = withSingElfClassI classS
if hShStrNdx == 0
then return BSL.empty
else do
strs <- index' ss hShStrNdx
return $ getSectionData bs strs
copyElf :: MonadCatch m => BSL.ByteString -> m BSL.ByteString
copyElf bs = parseElf bs >>= serializeElf
---------------------------------------------------------------------
-- This is for examples/README.md
withHeader :: BSL.ByteString -> (forall a . SingElfClassI a => HeaderXX a -> b) -> Either String b
withHeader bs f =
case decodeOrFail bs of
Left (_, _, err) -> Left err
Right (_, _, (H.Header classS hxx)) -> Right $ withSingElfClassI classS f hxx
printHeaderFile :: FilePath -> IO (Doc ())
printHeaderFile path = do
bs <- fromStrict <$> BS.readFile path
$eitherAddContext' $ withHeader bs printHeader
printHeadersFile :: FilePath -> IO (Doc ())
printHeadersFile path = do
bs <- fromStrict <$> BS.readFile path
Headers classS hdr ss ps <- parseHeaders bs
return $ withSingElfClassI classS $ printHeaders hdr ss ps
printStrTableFile :: FilePath -> IO (Doc ())
printStrTableFile path = do
bs <- readFileLazy path
hdrs <- parseHeaders bs
st <- getStringTable hdrs bs
printStringTable st
printCopyStrTableFile :: FilePath -> IO (Doc ())
printCopyStrTableFile path = do
bs <- readFileLazy path
bs' <- copyElf bs
hdrs <- parseHeaders bs'
st <- getStringTable hdrs bs'
printStringTable st
printRBuilderFile :: FilePath -> IO (Doc ())
printRBuilderFile path = do
bs <- readFileLazy path
hdrs <- parseHeaders bs
printLayout hdrs bs
printCopyRBuilderFile :: FilePath -> IO (Doc ())
printCopyRBuilderFile path = do
bs <- readFileLazy path
bs' <- copyElf bs
hdrs <- parseHeaders bs'
printLayout hdrs bs'
printElfFile :: FilePath -> IO (Doc ())
printElfFile path = do
bs <- readFileLazy path
e <- parseElf bs
printElf e
printCopyElfFile :: FilePath -> IO (Doc ())
printCopyElfFile path = do
bs <- readFileLazy path
bs' <- copyElf bs
e <- parseElf bs'
printElf e
-----------------------------------------------------------------------
testHeader64 :: H.Header
testHeader64 = H.Header SELFCLASS64 (HeaderXX ELFDATA2LSB 0 0 0 0 0 0 0 0 0 0 0 0 0)
testHeader32 :: H.Header
testHeader32 = H.Header SELFCLASS32 (HeaderXX ELFDATA2MSB 0 0 0 0 0 0 0 0 0 0 0 0 0)
testSection64 :: SectionXX 'ELFCLASS64
testSection64 = SectionXX 0 0 0 0 0 0 0 0 0 0
testSection32 :: SectionXX 'ELFCLASS32
testSection32 = SectionXX 0 0 0 0 0 0 0 0 0 0
testSegment64 :: SegmentXX 'ELFCLASS64
testSegment64 = SegmentXX 0 0 0 0 0 0 0 0
testSegment32 :: SegmentXX 'ELFCLASS32
testSegment32 = SegmentXX 0 0 0 0 0 0 0 0
testSymbolTableEntry64 :: SymbolXX 'ELFCLASS64
testSymbolTableEntry64 = SymbolXX 0 0 0 0 0 0
testSymbolTableEntry32 :: SymbolXX 'ELFCLASS32
testSymbolTableEntry32 = SymbolXX 0 0 0 0 0 0
mkSizeTest :: Binary a => String -> a -> Int64 -> TestTree
mkSizeTest name v s = testCase name (len @?= s)
where
len = BSL.length $ encode v
hdrSizeTests :: TestTree
hdrSizeTests = testGroup "header size" [ mkSizeTest "header 64" testHeader64 (headerSize ELFCLASS64)
, mkSizeTest "header 32" testHeader32 (headerSize ELFCLASS32)
, mkSizeTest "section 64" (Le testSection64) (sectionTableEntrySize ELFCLASS64)
, mkSizeTest "section 32" (Be testSection32) (sectionTableEntrySize ELFCLASS32)
, mkSizeTest "segment 64" (Le testSegment64) (segmentTableEntrySize ELFCLASS64)
, mkSizeTest "segment 32" (Be testSegment32) (segmentTableEntrySize ELFCLASS32)
, mkSizeTest "symbol table entry 64" (Le testSymbolTableEntry64) (symbolTableEntrySize ELFCLASS64)
, mkSizeTest "symbol table entry 32" (Be testSymbolTableEntry32) (symbolTableEntrySize ELFCLASS32)
]
elfsForHeader :: [String]
elfsForHeader = [ "testdata/orig/bloated"
, "testdata/orig/tiny"
, "testdata/orig/vdso"
]
main :: IO ()
main = do
setLocaleEncoding utf8
elfs <- traverseDir "testdata" isElf
defaultMain $ testGroup "elf" [ hdrSizeTests
, testGroup "headers round trip" (mkTest <$> elfs)
, testGroup "elf headers golden" (mkGoldenTest "elf_header" printHeaderFile <$> elfsForHeader)
, testGroup "header golden" (mkGoldenTest "header" printHeadersFile <$> elfs)
, testGroup "string table golden" (mkGoldenTest "strtable" printStrTableFile <$> elfs)
, testGroup "layout golden" (mkGoldenTest "layout" printRBuilderFile <$> elfs)
, testGroup "elf golden" (mkGoldenTest "elf" printElfFile <$> elfs)
, testGroup "string table copy" (mkGoldenTestOSuffix "strtable" "copy" printCopyStrTableFile <$> elfs)
, testGroup "layout copy" (mkGoldenTestOSuffix "layout" "copy" printCopyRBuilderFile <$> elfs)
, testGroup "elf copy" (mkGoldenTestOSuffix "elf" "copy" printCopyElfFile <$> elfs)
]