module LLVM.Analysis.Util.Testing (
TestDescriptor(..),
testAgainstExpected,
buildModule,
readInputAndExpected
) where
import Control.Monad ( when )
import System.Directory ( findExecutable )
import System.Environment ( getEnv )
import System.Exit ( ExitCode(ExitSuccess) )
import System.FilePath
import System.FilePath.Glob
import System.IO.Error
import System.IO.Temp
import System.Process as P
import Test.Framework ( defaultMain, Test )
import Test.Framework.Providers.HUnit
import LLVM.Analysis
data TestDescriptor =
forall a. (Read a) => TestDescriptor {
testPattern :: String,
testExpectedMapping :: FilePath -> FilePath,
testResultBuilder :: Module -> a,
testResultComparator :: String -> a -> a -> IO ()
}
readInputAndExpected :: (Read a)
=> [String]
-> (FilePath -> IO (Either String Module))
-> (FilePath -> FilePath)
-> FilePath
-> IO (FilePath, Module, a)
readInputAndExpected optOpts parseFile expectedFunc inputFile = do
let exFile = expectedFunc inputFile
exContent <- readFile exFile
let expected = length exContent `seq` read exContent
m <- buildModule optOpts parseFile inputFile
return $ either error (\m' -> (inputFile, m', expected)) m
testAgainstExpected :: [String]
-> (FilePath -> IO (Either String Module))
-> [TestDescriptor]
-> IO ()
testAgainstExpected optOpts parseFile testDescriptors = do
caseSets <- mapM mkDescriptorSet testDescriptors
defaultMain $ concat caseSets
where
mkDescriptorSet :: TestDescriptor -> IO [Test]
mkDescriptorSet TestDescriptor { testPattern = pat
, testExpectedMapping = mapping
, testResultBuilder = br
, testResultComparator = cmp
} = do
testInputFiles <- namesMatching pat
inputsAndExpecteds <- mapM (readInputAndExpected optOpts parseFile mapping) testInputFiles
mapM (mkTest br cmp) inputsAndExpecteds
mkTest br cmp (file, m, expected) = do
let actual = br m
return $ testCase file $ cmp file expected actual
optify :: [String] -> FilePath -> FilePath -> IO ()
optify args inp optFile = do
opt <- findOpt
let cmd = P.proc opt ("-o" : optFile : inp : args)
(_, _, _, p) <- createProcess cmd
rc <- waitForProcess p
when (rc /= ExitSuccess) (ioError (userError ("LLVM.Analysis.Util.Testing.optify: Could not optimize " ++ inp)))
buildModule :: [String]
-> (FilePath -> IO (Either String Module))
-> FilePath
-> IO (Either String Module)
buildModule optOpts parseFile inputFilePath = do
clang <- catchIOError (getEnv "LLVM_CLANG") (const (return "clang"))
clangxx <- catchIOError (getEnv "LLVM_CLANGXX") (const (return "clang++"))
case takeExtension inputFilePath of
".ll" -> simpleBuilder inputFilePath
".bc" -> simpleBuilder inputFilePath
".c" -> clangBuilder inputFilePath clang
".C" -> clangBuilder inputFilePath clangxx
".cxx" -> clangBuilder inputFilePath clangxx
".cpp" -> clangBuilder inputFilePath clangxx
_ -> return $ Left ("LLVM.Analysis.Util.Testing.buildModule: No build method for test input " ++ inputFilePath)
where
simpleBuilder inp
| null optOpts = parseFile inp
| otherwise =
withSystemTempFile ("opt_" ++ takeFileName inp) $ \optFname _ -> do
optify optOpts inp optFname
parseFile optFname
clangBuilder inp driver =
withSystemTempFile ("base_" ++ takeFileName inp) $ \baseFname _ -> do
let baseCmd = proc driver ["-emit-llvm", "-o" , baseFname, "-c", inp]
(_, _, _, p) <- createProcess baseCmd
rc <- waitForProcess p
when (rc /= ExitSuccess) (ioError (userError ("LLVM.Analysis.Util.Testing.buildModule.clangBuilder: Could not compile input to bitcode: " ++ inp)))
case null optOpts of
True -> parseFile baseFname
False ->
withSystemTempFile ("opt_" ++ takeFileName inp) $ \optFname _ -> do
optify optOpts baseFname optFname
parseFile optFname
findOpt :: IO FilePath
findOpt = do
let fbin = findBin [ "opt", "opt-3.2", "opt-3.1", "opt-3.0" ]
catchIOError (getEnv "LLVM_OPT") (const fbin)
where
err e = ioError $ mkIOError doesNotExistErrorType e Nothing Nothing
findBin [] = err "No opt binary found available"
findBin (bin:bins) = do
b <- findExecutable bin
case b of
Just e -> return e
Nothing -> findBin bins