module Shaker.Reflexivite( ModuleMapping(..) ,RunnableFunction(..) ,runReflexivite ,runFunction -- * Template haskell generator ,listHunit ,listProperties ) where import OccName (occNameString) import Name (nameOccName) import Var (varName) import Data.List import Data.Maybe import GHC import GHC.Paths import Outputable import Shaker.Type import Shaker.Action.Compile import Shaker.SourceHelper import Unsafe.Coerce import Control.Monad.Reader import Control.Arrow import Language.Haskell.TH -- | Mapping between module name (to import) and test to execute data ModuleMapping = ModuleMapping { cfModuleName :: String -- ^ Complete name of the module ,cfHunitName :: [String] -- ^ Hunit test function names ,cfPropName :: [String] -- ^ QuickCheck test function names } deriving Show data RunnableFunction = RunnableFunction { cfModule :: [String] ,cfFunctionName :: String -- The function name. Should have IO() as signature } deriving Show -- | Collect all non-main modules with their test function associated runReflexivite :: Shaker IO [ModuleMapping] runReflexivite = do cpList <- asks compileInputs let cpIn = mergeCompileInputsSources cpList cfFlList <- lift $ constructCompileFileList cpIn lift $ runGhc (Just libdir) $ do _ <- ghcCompile $ runReader (setAllHsFilesAsTargets cpIn >>= removeFileWithMain >>=removeFileWithTemplateHaskell) cfFlList modSummaries <- getModuleGraph mapM getModuleMapping modSummaries -- | Compile, load and run the given function runFunction :: RunnableFunction -> Shaker IO() runFunction (RunnableFunction funModuleName fun) = do cpList <- asks compileInputs let cpIn = mergeCompileInputsSources cpList cfFlList <- lift $ constructCompileFileList cpIn dynFun <- lift $ runGhc (Just libdir) $ do _ <- ghcCompile $ runReader (setAllHsFilesAsTargets cpIn >>= removeFileWithMain ) cfFlList configureContext funModuleName value <- compileExpr fun do let value' = unsafeCoerce value :: a return value' _ <- lift dynFun return () where configureContext [] = getModuleGraph >>= \mGraph -> setContext [] $ map ms_mod mGraph configureContext imports = mapM (\a -> findModule (mkModuleName a) Nothing ) imports >>= \m -> setContext [] m -- | Collect module name and tests name for the given module getModuleMapping :: (GhcMonad m) => ModSummary -> m ModuleMapping getModuleMapping modSum = do mayModuleInfo <- getModuleInfo $ ms_mod modSum let props = getQuickCheckFunction mayModuleInfo let hunits = getHunitFunctions mayModuleInfo return $ ModuleMapping modName hunits props where modName = (moduleNameString . moduleName . ms_mod) modSum getQuickCheckFunction :: Maybe ModuleInfo -> [String] getQuickCheckFunction = getFunctionNameWithPredicate ("prop_" `isPrefixOf`) getHunitFunctions :: Maybe ModuleInfo -> [String] getHunitFunctions = getFunctionTypeWithPredicate (== "Test.HUnit.Base.Test") getFunctionTypeWithPredicate :: (String -> Bool) -> Maybe ModuleInfo -> [String] getFunctionTypeWithPredicate _ Nothing = [] getFunctionTypeWithPredicate predicat (Just modInfo) = map snd $ filter ( predicat . fst) typeList where idList = getIdList modInfo typeList = map ((showPpr . idType) &&& getFunctionNameFromId ) idList getFunctionNameWithPredicate :: (String -> Bool) -> Maybe ModuleInfo -> [String] getFunctionNameWithPredicate _ Nothing = [] getFunctionNameWithPredicate predicat (Just modInfo) = filter predicat nameList where idList = getIdList modInfo nameList = map getFunctionNameFromId idList getFunctionNameFromId :: Id -> String getFunctionNameFromId = occNameString . nameOccName . varName getIdList :: ModuleInfo -> [Id] getIdList modInfo = mapMaybe tyThingToId $ modInfoTyThings modInfo tyThingToId :: TyThing -> Maybe Id tyThingToId (AnId tyId) = Just tyId tyThingToId _ = Nothing getQuickCheckProperty :: [ModuleMapping] -> [Exp] getQuickCheckProperty = concatMap getQuickCheckProperty' getQuickCheckProperty' :: ModuleMapping -> [Exp] getQuickCheckProperty' modMap = map getSingleQuickCheck $ cfPropName modMap getSingleQuickCheck :: String -> Exp getSingleQuickCheck propName = InfixE (Just printName) (VarE $ mkName ">>") (Just quickCall) where quickCall = (AppE (VarE $ mkName "quickCheck" ) . VarE . mkName) propName printName = AppE (VarE $ mkName "putStrLn") (LitE (StringL propName)) getHunit :: [ModuleMapping] -> [Exp] getHunit = concatMap getHunit' getHunit' :: ModuleMapping -> [Exp] getHunit' modMap = map (VarE . mkName) $ cfHunitName modMap -- | List the quickeck properties of the project. -- see "Shaker.TestTH" listProperties :: ShakerInput -> ExpQ listProperties shIn = do modMaps <- runIO $ runReaderT runReflexivite shIn return $ ListE $ getQuickCheckProperty modMaps -- | List all test case of the project. -- see "Shaker.TestTH" listHunit :: ShakerInput -> ExpQ listHunit shIn = do modMaps <- runIO $ runReaderT runReflexivite shIn return $ ListE $ getHunit modMaps