packages feed

MuCheck 0.2.0.0 → 0.2.1.0

raw patch · 12 files changed

+291/−159 lines, 12 filesdep +MissingHdep +temporarydep −hspec-corePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: MissingH, temporary

Dependencies removed: hspec-core

API changes (from Hackage documentation)

- Test.MuCheck.Interpreter: mutantCheckSummary :: (Summarizable a, Show a) => ([String] -> [InterpreterOutput a] -> Summary) -> [String] -> String -> [String] -> FilePath -> IO ()
- Test.MuCheck.MuOp: mkMp' :: (MonadPlus m, Typeable a) => MuOp -> a -> m a
- Test.MuCheck.Mutation: getDecls :: Module -> [Decl]
- Test.MuCheck.Mutation: parseModuleFromFile :: String -> Module
- Test.MuCheck.Mutation: selectIntOps :: (Data a, Eq a) => a -> [MuOp]
- Test.MuCheck.Mutation: selectValOps' :: (Data a, Eq a, Typeable b, Mutable b) => (b -> Bool) -> (b -> [b]) -> a -> [MuOp]
- Test.MuCheck.TestAdapter: type MutantFilename = String
- Test.MuCheck.Utils.Common: replace :: Eq a => (a, a) -> [a] -> [a]
+ Test.MuCheck.Config: MutateNegateGuards :: MuVars
+ Test.MuCheck.Config: MutateNegateIfElse :: MuVars
+ Test.MuCheck.Config: MutatePatternMatch :: MuVars
+ Test.MuCheck.Config: MutateValues :: MuVars
+ Test.MuCheck.Config: data MuVars
+ Test.MuCheck.Config: getSample :: MuVars -> Config -> Rational
+ Test.MuCheck.Interpreter: evaluateMutants :: (Summarizable a, Show a) => ([Mutant] -> [InterpreterOutput a] -> Summary) -> [Mutant] -> [String] -> FilePath -> IO ()
+ Test.MuCheck.MuOp: mkMpMuOp :: (MonadPlus m, Typeable a) => MuOp -> a -> m a
+ Test.MuCheck.Mutation: genMutantsForSrc :: Config -> String -> String -> (MuVars -> [MuOp] -> [MuOp]) -> [Mutant]
+ Test.MuCheck.Mutation: getASTFromStr :: String -> Module
+ Test.MuCheck.Mutation: getFunc :: String -> Module -> Decl
+ Test.MuCheck.Mutation: replaceDef :: Decl -> Decl -> Module -> [Decl]
+ Test.MuCheck.Mutation: sampler :: RandomGen g => Config -> g -> MuVars -> [t] -> [t]
+ Test.MuCheck.Mutation: selectBLitOps :: Decl -> [MuOp]
+ Test.MuCheck.Mutation: selectLitOps :: Decl -> [MuOp]
+ Test.MuCheck.TestAdapter: type Mutant = String
+ Test.MuCheck.Utils.Common: curryM :: (t1 -> t2 -> m t) -> (t1, t2) -> m t
+ Test.MuCheck.Utils.Common: genRandomSeed :: IO StdGen
+ Test.MuCheck.Utils.Common: rSample :: Int -> [t] -> IO [t]
+ Test.MuCheck.Utils.Common: replaceFst :: Eq a => (a, a) -> [a] -> [a]
- Test.MuCheck: mucheck :: (Summarizable a, Show a) => ([String] -> [InterpreterOutput a] -> Summary) -> String -> FilePath -> String -> [String] -> IO ()
+ Test.MuCheck: mucheck :: (Summarizable a, Show a) => ([Mutant] -> [InterpreterOutput a] -> Summary) -> String -> FilePath -> [String] -> IO ()
- Test.MuCheck.Mutation: genMutants :: String -> FilePath -> IO Int
+ Test.MuCheck.Mutation: genMutants :: String -> FilePath -> IO [Mutant]
- Test.MuCheck.Mutation: genMutantsWith :: Config -> String -> FilePath -> IO Int
+ Test.MuCheck.Mutation: genMutantsWith :: Config -> String -> FilePath -> IO [Mutant]
- Test.MuCheck.Mutation: selectGuardedBoolNegOps :: (Data a, Eq a) => a -> [MuOp]
+ Test.MuCheck.Mutation: selectGuardedBoolNegOps :: Decl -> [MuOp]
- Test.MuCheck.Mutation: selectIfElseBoolNegOps :: (Data a, Eq a) => a -> [MuOp]
+ Test.MuCheck.Mutation: selectIfElseBoolNegOps :: Decl -> [MuOp]
- Test.MuCheck.Mutation: selectValOps :: (Data a, Eq a, Typeable b, Mutable b, Eq b) => (b -> Bool) -> [b -> b] -> a -> [MuOp]
+ Test.MuCheck.Mutation: selectValOps :: (Typeable b, Mutable b) => (b -> Bool) -> (b -> [b]) -> Decl -> [MuOp]
- Test.MuCheck.TestAdapter: testSummary :: Summarizable s => [MutantFilename] -> [InterpreterOutput s] -> Summary
+ Test.MuCheck.TestAdapter: testSummary :: Summarizable s => [Mutant] -> [InterpreterOutput s] -> Summary

Files

MuCheck.cabal view
@@ -1,5 +1,5 @@ name:                MuCheck-version:             0.2.0.0+version:             0.2.1.0 synopsis:            Automated Mutation Testing description:         This package defines a mutation analysis library for haskell                      programs. It does this by parsing the haskell source, and@@ -25,7 +25,7 @@ source-repository    this   type:              git   location:          https://bitbucket.org/osu-testing/mucheck.git-  tag:               0.2.0.0+  tag:               0.2.1.0  library   exposed-modules:  Test.MuCheck,@@ -47,10 +47,10 @@                     hint >= 0.3.1.0,                     mtl>=2.1.2,                     filepath >= 1.1.0,-                    hspec>= 2.0,-                    hspec-core >= 2.0,                     random >= 1.0.0,-                    directory >= 1.2.1.0+                    directory >= 1.2.1.0,+                    temporary >= 1.1,+                    MissingH >= 1.3   default-language: Haskell2010   hs-source-dirs:   src @@ -67,8 +67,10 @@                     mtl>=2.1.2,                     filepath >= 1.1.0,                     hspec>= 2.0,-                    hspec-core >= 2.0,                     MuCheck,                     random >= 1.0.0,-                    directory >= 1.2.1.0+                    directory >= 1.2.1.0,+                    temporary >= 1.1,+                    MissingH >= 1.3+  default-language: Haskell2010 
changes.md view
@@ -1,5 +1,14 @@ # Changelog +## [0.2.1.0] (Rahul Gopinath)+  * Remove need to pass in module name+  * Add new literal mutators including booleans+  * Refactor Mutation so that IO and Rand are pushed further out, and a sampler is introduced that user can override.+  * Fix the bug where maxMutants were just the first n mutants, rather than n being randomly sampled.+  * Move writefile to front where more choices about it can be made.+  * We no longer require modulename to be passed in.+  * Fix bug in replaceFst+ ## [0.2.0.0] (Rahul Gopinath)   * Better documentation   * Add tests for SYB once
src/Test/MuCheck.hs view
@@ -3,14 +3,14 @@ import Control.Monad (void)  import Test.MuCheck.Mutation+import Test.MuCheck.Config import Test.MuCheck.Utils.Common-import Test.MuCheck.Interpreter (mutantCheckSummary)+import Test.MuCheck.Interpreter (evaluateMutants) import Test.MuCheck.TestAdapter  -- | Perform mutation analysis-mucheck :: (Summarizable a, Show a) => ([String] -> [InterpreterOutput a] -> Summary) -> String -> FilePath -> String -> [String] -> IO ()-mucheck resFn mFn file modulename args = do-  numMutants <- genMutants mFn file-  let muts = take numMutants $ genFileNames file-  void $ mutantCheckSummary resFn muts modulename args ("./mucheck-" ++ mFn ++ ".log")+mucheck :: (Summarizable a, Show a) => ([Mutant] -> [InterpreterOutput a] -> Summary) -> String -> FilePath -> [String] -> IO ()+mucheck resFn mFn file args = do+  mutants <- genMutants mFn file >>= rSample (maxNumMutants defaultConfig)+  void $ evaluateMutants resFn mutants args ("./mucheck-" ++ mFn ++ ".log") 
src/Test/MuCheck/Config.hs view
@@ -4,6 +4,7 @@ import Test.MuCheck.MuOp import Test.MuCheck.Operators (allOps) +-- | The knob controlling if we want first order mutation. data GenerationMode   = FirstOrderOnly   | FirstAndHigherOrder@@ -66,4 +67,18 @@   , doNegateGuards = 1.0   , maxNumMutants = 300   , genMode = FirstOrderOnly }++-- | Enumeration of different kinds of mutations+data MuVars = MutatePatternMatch+            | MutateValues+            | MutateNegateIfElse+            | MutateNegateGuards++-- | getSample returns the fraction in config corresponding to the enum passed+-- in+getSample :: MuVars -> Config -> Rational+getSample MutatePatternMatch = doMutatePatternMatches+getSample MutateValues       = doMutateValues+getSample MutateNegateIfElse = doNegateIfElse+getSample MutateNegateGuards = doNegateGuards 
src/Test/MuCheck/Interpreter.hs view
@@ -1,21 +1,24 @@ -- | The entry point for mucheck-module Test.MuCheck.Interpreter (mutantCheckSummary) where+module Test.MuCheck.Interpreter (evaluateMutants) where  import qualified Language.Haskell.Interpreter as I-import Control.Monad.Trans ( liftIO )+import Control.Monad.Trans (liftIO)+import Control.Monad (liftM) import Data.Typeable import Test.MuCheck.Utils.Print (showA, showAS, (./.), catchOutput) import Data.Either (partitionEithers, rights) import Data.List(groupBy, sortBy) import Data.Function (on)+import System.Directory (createDirectoryIfMissing)+import Data.Hash.MD5 (md5s, Str(..))  import Test.MuCheck.TestAdapter  -- | Given the list of tests suites to check, run one test suite at a time on -- all mutants.-mutantCheckSummary :: (Summarizable a, Show a) => ([String] -> [InterpreterOutput a] -> Summary) -> [String] -> String -> [String] -> FilePath -> IO ()-mutantCheckSummary testSummaryFn mutantFiles topModule evalSrcLst logFile  = do-  results <- mapM (runCodeOnMutants mutantFiles topModule) evalSrcLst+evaluateMutants :: (Summarizable a, Show a) => ([Mutant] -> [InterpreterOutput a] -> Summary) -> [Mutant] -> [String] -> FilePath -> IO ()+evaluateMutants testSummaryFn mutantFiles evalSrcLst logFile  = do+  results <- mapM (runCodeOnMutants mutantFiles) evalSrcLst   let singleTestSummaries = zip evalSrcLst $ map (mySummaryFn testSummaryFn mutantFiles) results       tssum  = multipleCheckSummary (isSuccess . snd) results   -- print results to terminal@@ -51,7 +54,7 @@                   tsumOthers::Int,                   tsumLog::String} -mySummaryFn :: (Summarizable b, Eq a1) => ([a1] -> [Either a (a1, b)] -> Summary) -> [a1] -> [Either a (a1, b)] -> TSum+mySummaryFn :: Summarizable a => ([Mutant] -> [InterpreterOutput a] -> Summary) -> [Mutant] -> [InterpreterOutput a] -> TSum mySummaryFn testSummaryFn mutantFiles results = TSum {     tsumNumMutants = r,     tsumLoadError = l,@@ -69,22 +72,29 @@   -- | Run one test suite on all mutants-runCodeOnMutants :: Typeable t => [String] -> String -> String -> IO [InterpreterOutput t]-runCodeOnMutants mutantFiles topModule evalStr = mapM (evalMyStr evalStr) mutantFiles-  where evalMyStr eStr file = do putStrLn $ ">" ++ ":" ++ file ++ ":" ++ topModule ++ ":" ++ evalStr-                                 (res,_) <- catchOutput (I.runInterpreter (evalMethod file topModule eStr))-                                 return res+runCodeOnMutants :: Typeable t => [Mutant] -> String -> IO [InterpreterOutput t]+runCodeOnMutants mutantFiles evalStr = mapM (evalMyStr evalStr) mutantFiles+  where evalMyStr eStr m = liftM fst $ do+          createDirectoryIfMissing True ".mutants"+          let fPath = ".mutants/" ++ md5s (Str m) ++ ".hs"+          -- Hint does not provide us a way to evaluate the module without+          -- writing it to disk first, so we go for this hack.+          -- We write the temporary file to disk, run interpreter on it, get+          -- the result and remove the file+          writeFile fPath m+          putStrLn $ "> " ++ fPath ++ " " ++ evalStr+          catchOutput (I.runInterpreter (evalMethod fPath eStr))  -- | Given the filename, modulename, test to evaluate, evaluate, and return result as a pair. -- -- > t = I.runInterpreter (evalMethod -- >        "Examples/QuickCheckTest.hs"--- >        "Examples.QuickCheckTest"--- >        "quickCheckResult idEmp)-evalMethod :: (I.MonadInterpreter m, Typeable t) => String -> String -> String -> m (String, t)-evalMethod fileName topModule evalStr = do+-- >        "quickCheckResult idEmp")+evalMethod :: (I.MonadInterpreter m, Typeable t) => String -> String -> m (String, t)+evalMethod fileName evalStr = do   I.loadModules [fileName]-  I.setTopLevelModules [topModule]+  ms <- I.getLoadedModules+  I.setTopLevelModules ms   result <- I.interpret evalStr (I.as :: (Typeable a => IO a)) >>= liftIO   return (fileName, result) 
src/Test/MuCheck/MuOp.hs view
@@ -1,9 +1,11 @@+{-#  LANGUAGE Rank2Types #-}+-- | Mutation operators module Test.MuCheck.MuOp (MuOp           , Mutable(..)           , (==>*)           , (*==>*)           , (~~>)-          , mkMp'+          , mkMpMuOp           , same           ) where @@ -11,6 +13,7 @@ import qualified Data.Generics as G import Control.Monad (MonadPlus, mzero) +-- | MuOp constructor used to specify mutation transformation data MuOp = N  (Name, Name)           | QN (QName, QName)           | QO (QOp, QOp)@@ -22,73 +25,79 @@  -- boilerplate code +apply :: (forall a. (Eq a, G.Typeable a, Show a) => (a,a) -> c) -> MuOp -> c+apply f (N  m) = f m+apply f (QN m) = f m+apply f (QO m) = f m+apply f (E  m) = f m+apply f (D  m) = f m+apply f (L  m) = f m+apply f (G  m) = f m+ -- | The function `same` applies on a `MuOP` determining if transformation is -- between same values. same :: MuOp -> Bool-same (N (a,b)) = a == b-same (QN (a,b)) = a == b-same (QO (a,b)) = a == b-same (E (a,b)) = a == b-same (D (a,b)) = a == b-same (L (a,b)) = a == b-same (G (a,b)) = a == b+same = apply $ uncurry (==) -mkMp' :: (MonadPlus m, G.Typeable a) => MuOp -> a -> m a-mkMp' (N (s,t))  = G.mkMp (s ~~> t)-mkMp' (QN (s,t)) = G.mkMp (s ~~> t)-mkMp' (QO (s,t)) = G.mkMp (s ~~> t)-mkMp' (E (s,t))  = G.mkMp (s ~~> t)-mkMp' (D (s,t))  = G.mkMp (s ~~> t)-mkMp' (L (s,t))  = G.mkMp (s ~~> t)-mkMp' (G (s,t))  = G.mkMp (s ~~> t)+-- | A wrapper over mkMp+mkMpMuOp :: (MonadPlus m, G.Typeable a) => MuOp -> a -> m a+mkMpMuOp = apply $ G.mkMp . (uncurry (~~>)) +-- | Show a specified mutation showM :: (Show a1, Show a) => (a, a1) -> String showM (s, t) = "\n" ++ show s ++ " ==> " ++ show t +-- | MuOp instance for Show instance Show MuOp where-    show (N a)  = showM a-    show (QN a) = showM a-    show (QO a) = showM a-    show (E a)  = showM a-    show (D a)  = showM a-    show (L a)  = showM a-    show (G a)  = showM a+  show = apply showM  -- end boilerplate code --- Mutation operation representing translation from one fn to another fn.+-- | Mutation operation representing translation from one fn to another fn. class Mutable a where-    (==>) :: a -> a -> MuOp+  (==>) :: a -> a -> MuOp +-- | The function `==>*` pairs up the given element with all elements of the+-- second list, and applies `==>` on them. (==>*) :: Mutable a => a -> [a] -> [MuOp]-(==>*) x lst = map (\i -> x ==> i) lst+(==>*) x lst = map (x ==>) lst +-- | The function `*==>*` pairs up all elements of first list with all elements+-- of second list and applies `==>` between them. (*==>*) :: Mutable a => [a] -> [a] -> [MuOp] xs *==>* ys = concatMap (==>* ys) xs +-- | The function `~~>` accepts two values, and returns a function+-- that if given a value equal to first, returns second -- we handle x ~~> x separately (~~>) :: (MonadPlus m, Eq a) => a -> a -> a -> m a x ~~> y = \z -> if z == x then return y else mzero --- instances-+-- | Name instance for Mutable instance Mutable Name where-    (==>) = (N .) . (,)-    +  (==>) = (N .) . (,)++-- | QName instance for Mutable instance Mutable QName where-    (==>) = (QN .) . (,)-    +  (==>) = (QN .) . (,)++-- | QOp instance for Mutable instance Mutable QOp where-    (==>) = (QO .) . (,)+  (==>) = (QO .) . (,) +-- | Exp instance for Mutable instance Mutable Exp where-    (==>) = (E .) . (,)-    +  (==>) = (E .) . (,)++-- | Exp instance for Mutable instance Mutable Decl where-    (==>) = (D .) . (,)-    +  (==>) = (D .) . (,)++-- | Literal instance for Mutable instance Mutable Literal where-    (==>) = (L .) . (,)-    +  (==>) = (L .) . (,)++-- | GuardedRhs instance for Mutable instance Mutable GuardedRhs where-    (==>) = (G .) . (,)+  (==>) = (G .) . (,)+
src/Test/MuCheck/Mutation.hs view
@@ -2,45 +2,61 @@ -- | Mutation happens here. module Test.MuCheck.Mutation where -import Language.Haskell.Exts(Literal(Int), Exp(App, Var, If), QName(UnQual),+import Language.Haskell.Exts(Literal(Int, Char, Frac, String, PrimInt, PrimChar, PrimFloat, PrimDouble, PrimWord, PrimString),+        Exp(App, Var, If), QName(UnQual),         Stmt(Qualifier), Module(Module),         Name(Ident, Symbol), Decl(FunBind, PatBind),-        Pat(PVar), Match(Match), GuardedRhs(GuardedRhs), +        Pat(PVar), Match(Match), GuardedRhs(GuardedRhs),         prettyPrint, fromParseResult, parseFileContents)-import Data.Generics (Data, Typeable, mkMp, listify)+import Data.Generics (Typeable, mkMp, listify) import Data.List(nub, (\\), permutations)-import Control.Monad (liftM, zipWithM)-import System.Random-import Data.Time.Clock.POSIX+import System.Random (RandomGen)  import Test.MuCheck.MuOp import Test.MuCheck.Utils.Syb import Test.MuCheck.Utils.Common import Test.MuCheck.Config+import Test.MuCheck.TestAdapter  -- | The `genMutants` function is a wrapper to genMutantsWith with standard -- configuraton-genMutants :: String -> FilePath -> IO Int+genMutants :: String -> FilePath -> IO [Mutant] genMutants = genMutantsWith defaultConfig  -- | The `genMutantsWith` function takes configuration function to mutate, -- filename the function is defined in, and produces mutants in the same -- directory as the filename, and returns the number of mutants produced.-genMutantsWith :: Config -> String -> FilePath -> IO Int-genMutantsWith args funcname filename  = liftM length $ do-    ast <- getASTFromFile filename-    g <- liftM (mkStdGen . round) getPOSIXTime-    let f = getFunc funcname ast+genMutantsWith :: Config -> String -> FilePath -> IO [Mutant]+genMutantsWith args func filename  = do+      g <- genRandomSeed+      f <- readFile filename+      return $ genMutantsForSrc defaultConfig func f (sampler args g) +-- | Wrapper around sampleF that returns correct sampling ratios according to+-- configuration passed.+sampler :: RandomGen g => Config -> g -> MuVars -> [t] -> [t]+sampler args g m = sampleF g (getSample m args)++-- | The `genMutantsForSrc` takes the function name to mutate, source where it+-- is defined, and a sampling function, and returns the mutated sources selected+-- using sampling function.+genMutantsForSrc :: Config -> String -> String -> (MuVars -> [MuOp] -> [MuOp]) -> [Mutant]+genMutantsForSrc args funcname src sampleFn = map prettyPrint programMutants+  where astMod = getASTFromStr src+        f = getFunc funcname astMod+         ops, swapOps, valOps, ifElseNegOps, guardedBoolNegOps :: [MuOp]         ops = relevantOps f (muOps args ++ valOps ++ ifElseNegOps ++ guardedBoolNegOps)-        swapOps = sampleF g (doMutatePatternMatches args) $ permMatches f ++ removeOnePMatch f-        valOps = sampleF g (doMutateValues args) $ selectIntOps f-        ifElseNegOps = sampleF g (doNegateIfElse args) $ selectIfElseBoolNegOps f-        guardedBoolNegOps = sampleF g (doNegateGuards args) $ selectGuardedBoolNegOps f+        swapOps = sampleFn MutatePatternMatch $ permMatches f ++ removeOnePMatch f+        valOps = sampleFn MutateValues $ selectLitOps f ++ selectBLitOps f+        ifElseNegOps = sampleFn MutateNegateIfElse $ selectIfElseBoolNegOps f+        guardedBoolNegOps = sampleFn MutateNegateGuards $ selectGuardedBoolNegOps f          patternMatchMutants, ifElseNegMutants, guardedNegMutants, operatorMutants, allMutants :: [Decl]-        allMutants = nub $ patternMatchMutants ++ operatorMutants ++ ifElseNegMutants ++ guardedNegMutants+        allMutants = nub $ patternMatchMutants +++                           operatorMutants +++                           ifElseNegMutants +++                           guardedNegMutants          patternMatchMutants = mutatesN swapOps f fstOrder         ifElseNegMutants = mutatesN ifElseNegOps f fstOrder@@ -49,40 +65,48 @@             FirstOrderOnly -> mutatesN ops f fstOrder             _              -> mutates ops f -        getFunc fname ast' = head $ listify (isFunctionD fname) ast'-        programMutants ast' =  map (putDecls ast) $ mylst ast'-        mylst ast' = [myfn ast' x | x <- take (maxNumMutants args) allMutants]-        myfn ast' fn = replace (getFunc funcname ast', fn) (getDecls ast')+        programMutants :: [Module]+        programMutants =  map (putDecls astMod) [replaceDef f fn astMod | fn <- allMutants] -    case ops ++ swapOps of-      [] -> return [] --  putStrLn "No applicable operator exists!"-      _  -> zipWithM writeFile (genFileNames filename) $ map prettyPrint (programMutants ast)-  where fstOrder = 1 -- first order-        getASTFromFile fname = liftM parseModuleFromFile $ readFile fname+        fstOrder = 1 -- first order --- | Mutating a function's code using a bunch of mutation operators--- (In all the three mutate functions, we assume working+-- | Replace old function definition with a new one in the AST+replaceDef :: Decl -> Decl -> Module -> [Decl]+replaceDef oldf newf (Module _ _ _ _ _ _ decls) = replaceFst (oldf, newf) decls++-- | Fetch the function definition from module+getFunc :: String -> Module -> Decl+getFunc fname ast = head $ listify (isFunctionD fname) ast++-- | Higher order mutation of a function's code using a bunch of mutation+-- operators (In all the three mutate functions, we assume working -- with functions declaration.) mutates :: [MuOp] -> Decl -> [Decl]-mutates ops m = filter (/= m) $ concatMap (mutatesN ops m) [1..]+mutates ops m = filter (/= m) $ concat [mutatesN ops m x | x <- enumFrom 1] --- the third argument specifies whether it's first order or higher order+-- | First and higher order mutation.+-- The third argument specifies whether it's first order or higher order mutatesN :: [MuOp] -> Decl -> Int -> [Decl] mutatesN ops ms 1 = concat [mutate op ms | op <- ops ]-mutatesN ops ms c = concat [mutatesN ops m 1 | m <- mutatesN ops ms (c-1)]+mutatesN ops ms c = concat [mutatesN ops m 1 | m <- mutatesN ops ms $ pred c] --- | Given a function, generate all mutants after applying applying --- op once (op might be applied at different places).E.g.:--- op = "<" ==> ">" and there are two instances of "<"+-- | Given a function, generate all mutants after applying applying+-- op once (op might be applied at different places).+-- E.g.: if the operator is (op = "<" ==> ">") and there are two instances of+-- "<" in the AST, then it will return two AST with each replaced. mutate :: MuOp -> Decl -> [Decl]-mutate op m = once (mkMp' op) m \\ [m]+mutate op m = once (mkMpMuOp op) m \\ [m]  -- | is the parsed expression the function we are looking for? isFunctionD :: String -> Decl -> Bool isFunctionD n (FunBind (Match _ (Ident n') _ _ _ _ : _)) = n == n' isFunctionD n (FunBind (Match _ (Symbol n') _ _ _ _ : _)) = n == n'+-- we also consider where clauses isFunctionD n (PatBind _ (PVar (Ident n')) _ _)          = n == n' isFunctionD _ _                                  = False+-- but not let, because it has a different type, and for our purposes+-- this is sufficient.+-- (Let Binds Exp) :: Exp  -- | Generate all operators for permutating pattern matches in -- a function. We don't deal with permutating guards and case for now.@@ -90,56 +114,82 @@ permMatches d@(FunBind ms) = d ==>* map FunBind (permutations ms \\ [ms]) permMatches _  = [] --- | generates transformations that removes one pattern match from a function+-- | Generates transformations that removes one pattern match from a function -- definition. removeOnePMatch :: Decl -> [MuOp] removeOnePMatch (FunBind [_]) = [] removeOnePMatch d@(FunBind ms) = d ==>* map FunBind (removeOneElem ms \\ [ms]) removeOnePMatch _  = [] --- | generate sub-arrays with one less element+-- | Generate sub-arrays with one less element removeOneElem :: Eq t => [t] -> [[t]] removeOneElem l = choose l (length l - 1)  -- AST/module-related operations --- | Parse a module. Input is the content of the file-parseModuleFromFile :: String -> Module-parseModuleFromFile inp = fromParseResult $ parseFileContents inp--getDecls :: Module -> [Decl]-getDecls (Module _ _ _ _ _ _ decls) = decls+-- | Returns the AST from the file+getASTFromStr :: String -> Module+getASTFromStr fname = fromParseResult $ parseFileContents fname +-- | Set the declaration in a module putDecls :: Module -> [Decl] -> Module putDecls (Module a b c d e f _) decls = Module a b c d e f decls --- Define all operations on a value-selectValOps :: (Data a, Eq a, Typeable b, Mutable b, Eq b) => (b -> Bool) -> [b -> b] -> a -> [MuOp]-selectValOps predicate fs m = concatMap (\x -> x ==>* map (\f -> f x) fs) vals-  where vals = nub $ listify predicate m--selectValOps' :: (Data a, Eq a, Typeable b, Mutable b) => (b -> Bool) -> (b -> [b]) -> a -> [MuOp]-selectValOps' predicate f m = concatMap (\x -> x ==>* f x) vals+-- | For valops, unlike functions, we specify how any given literal value might+-- change. So we take a predicate specifying how to recognize the literal+-- value, a list of mappings specifying how the literal can change, and the+-- AST, and recurse over the AST looking for literals that match our predicate.+-- When we find any, we apply the given list of mappings to them, and produce+-- a MuOp mapping between the original value and transformed value. This list+-- of MuOp mappings are then returned.+selectValOps :: (Typeable b, Mutable b) => (b -> Bool) -> (b -> [b]) -> Decl -> [MuOp]+selectValOps predicate f m = concat [ x ==>* f x |  x <- vals ]   where vals = listify predicate m -selectIntOps :: (Data a, Eq a) => a -> [MuOp]-selectIntOps m = selectValOps isInt [-      \(Int i) -> Int (i + 1),-      \(Int i) -> Int (i - 1),-      \(Int i) -> if abs i /= 1 then Int 0 else Int i,-      \(Int i) -> if abs (i-1) /= 1 then Int 1 else Int i] m-  where isInt (Int _) = True-        isInt _       = False+-- | Look for literal values in AST, and return applicable MuOp transforms.+-- Unfortunately booleans are not handled here.+selectLitOps :: Decl -> [MuOp]+selectLitOps m = selectValOps isLit convert m+  where isLit (Int _) = True+        isLit (PrimInt _) = True+        isLit (Char _) = True+        isLit (PrimChar _) = True+        isLit (Frac _) = True+        isLit (PrimFloat _) = True+        isLit (PrimDouble _) = True+        isLit (String _) = True+        isLit (PrimString _) = True+        isLit (PrimWord _) = True+        convert (Int i) = map Int $ nub [i + 1, i - 1, 0, 1]+        convert (PrimInt i) = map PrimInt $ nub [i + 1, i - 1, 0, 1]+        convert (Char c) = map Char [pred c, succ c]+        convert (PrimChar c) = map Char [pred c, succ c]+        convert (Frac f) = map Frac $ nub [f + 1.0, f - 1.0, 0.0, 1.1]+        convert (PrimFloat f) = map PrimFloat $ nub [f + 1.0, f - 1.0, 0.0, 1.1]+        convert (PrimDouble f) = map PrimDouble $ nub [f + 1.0, f - 1.0, 0.0, 1.1]+        convert (String _) = map String $ nub [""]+        convert (PrimString _) = map PrimString $ nub [""]+        convert (PrimWord i) = map PrimWord $ nub [i + 1, i - 1, 0, 1] --- | negating boolean in if/else statements-selectIfElseBoolNegOps :: (Data a, Eq a) => a -> [MuOp]-selectIfElseBoolNegOps m = selectValOps isIf [\(If e1 e2 e3) -> If (App (Var (UnQual (Ident "not"))) e1) e2 e3] m+-- | Convert Boolean Literals+selectBLitOps :: Decl -> [MuOp]+selectBLitOps m = selectValOps isLit convert m+  where isLit (Ident "True") = True+        isLit (Ident "False") = True+        isLit _ = False+        convert (Ident "True") = [Ident "False"]+        convert (Ident "False") = [Ident "True"]+        convert _ = []++-- | Negating boolean in if/else statements+selectIfElseBoolNegOps :: Decl -> [MuOp]+selectIfElseBoolNegOps m = selectValOps isIf (\(If e1 e2 e3) -> [If e1 e3 e2]) m   where isIf If{} = True         isIf _    = False --- | negating boolean in Guards-selectGuardedBoolNegOps :: (Data a, Eq a) => a -> [MuOp]-selectGuardedBoolNegOps m = selectValOps' isGuardedRhs negateGuardedRhs m+-- | Negating boolean in Guards+selectGuardedBoolNegOps :: Decl -> [MuOp]+selectGuardedBoolNegOps m = selectValOps isGuardedRhs negateGuardedRhs m   where isGuardedRhs GuardedRhs{} = True         boolNegate e@(Qualifier (Var (UnQual (Ident "otherwise")))) = [e]         boolNegate (Qualifier expr) = [Qualifier (App (Var (UnQual (Ident "not"))) expr)]
src/Test/MuCheck/Operators.hs view
@@ -1,3 +1,4 @@+-- | Available mutation operators module Test.MuCheck.Operators (comparators,                           predNums,                           binAriths,@@ -24,7 +25,7 @@ -- | functions on lists ["sum", "product", "maximum", "minimum", "head", "last"] arithLists = coupling (==>) $ map varfn ["sum", "product", "maximum", "minimum", "head", "last"] --- utilities+-- | wrapper to produce Function from String varfn :: String -> Exp varfn = Var . UnQual . Ident 
src/Test/MuCheck/TestAdapter.hs view
@@ -1,17 +1,28 @@+-- | Module for adapting test framekworks module Test.MuCheck.TestAdapter where  import qualified Language.Haskell.Interpreter as I import Data.Typeable++-- | Wrapper for interpreter output type InterpreterOutput a = Either I.InterpreterError (String, a) --- Class/Instance declaration-type MutantFilename = String+-- | Holding mutant information+type Mutant = String++-- | Summary of test run newtype Summary = Summary String++-- | Interface to be implemented by a test framework class Typeable s => Summarizable s where-  testSummary :: [MutantFilename] -> [InterpreterOutput s] -> Summary+  -- | Summary of a test run+  testSummary :: [Mutant] -> [InterpreterOutput s] -> Summary+  -- | Was the test run a success   isSuccess :: s -> Bool+  -- | Was the test run a failure   isFailure :: s -> Bool   isFailure = not . isSuccess+  -- | Was the test run neither (gaveup/timedout)   isOther :: s -> Bool   isOther x = not (isSuccess x) && not (isFailure x) 
src/Test/MuCheck/Utils/Common.hs view
@@ -4,6 +4,8 @@ import System.FilePath (splitExtension) import System.Random import Data.List+import Data.Time.Clock.POSIX (getPOSIXTime)+import Control.Monad (liftM)  -- | The `choose` function generates subsets of a given size choose :: [a] -> Int -> [[a]]@@ -22,21 +24,27 @@           newname :: Int -> String           newname i= name ++ "_" ++ show i ++ ext --- | The `replace` function replaces first element in a list given old and new values as a pair-replace :: Eq a => (a,a) -> [a] -> [a]-replace (o,n) lst = map replaceit lst-  where replaceit v-          | v == o = n-          | otherwise = v+-- | The `replaceFst` function replaces first matching element in a list given old and new values as a pair+replaceFst :: Eq a => (a,a) -> [a] -> [a]+replaceFst _ [] = []+replaceFst (o, n) (v:vs)+  | v == o = n : vs+  | otherwise   = v : replaceFst (o,n) vs  -- | The `sample` function takes a random generator and chooses a random sample -- subset of given size. sample :: (RandomGen g) => g -> Int -> [t] -> [t] sample _ 0 _ = []+sample _ n xs | length xs <= n = xs sample g n xs = val : sample g' (n - 1) (remElt idx xs)   where val = xs !! idx         (idx,g')  = randomR (0, length xs - 1) g +-- | Wrapper around sample providing the random seed+rSample :: Int -> [t] -> IO [t]+rSample n t = do g <- genRandomSeed+                 return $ sample g n t+ -- | The `sampleF` function takes a random generator, and a fraction and -- returns subset of size given by fraction sampleF :: (RandomGen g) => g -> Rational -> [t] -> [t]@@ -60,4 +68,13 @@ genSwapped :: [t] -> [[t]] genSwapped lst = map (\(x:y:_) -> swapElts x y lst) swaplst   where swaplst = choose [0..length lst - 1] 2++-- | Generate a random seed from the time.+genRandomSeed :: IO StdGen+genRandomSeed = liftM (mkStdGen . round) getPOSIXTime++-- | take a function of two args producing a monadic result, and apply it to+-- a pair+curryM :: (t1 -> t2 -> m t) -> (t1, t2) -> m t+curryM fn (a,b) = fn a b 
src/Test/MuCheck/Utils/Print.hs view
@@ -1,10 +1,14 @@+{-# LANGUAGE TupleSections #-}+-- | Common print utilities module Test.MuCheck.Utils.Print where import Debug.Trace import Data.List(intercalate)+import Control.Monad (liftM)  import GHC.IO.Handle import System.IO-import System.Directory+import System.Environment+import System.IO.Temp (withSystemTempFile)  -- | simple wrapper for adding a % at the end. (./.) :: (Show a, Integral a) => a -> a -> String@@ -18,21 +22,25 @@ showA :: Show a => [a] -> String showA =  showAS . map show +-- | convenience function for debug tt :: Show a => a -> a tt v = trace (">" ++ show v) v +-- | Capture output and err of an IO action catchOutput :: IO a -> IO (a,String) catchOutput f = do-  tmpd <- getTemporaryDirectory-  (tmpf, tmph) <- openTempFile tmpd "haskell_stdout"-  stdout_dup <- hDuplicate stdout-  stderr_dup <- hDuplicate stderr-  hDuplicateTo tmph stdout-  hDuplicateTo tmph stderr-  hClose tmph-  res <- f-  hDuplicateTo stdout_dup stdout-  hDuplicateTo stderr_dup stderr-  str <- readFile tmpf-  removeFile tmpf-  return (res,str)+  isdebug <- lookupEnv "MuDEBUG"+  case isdebug of +    Just _ -> liftM (,"") f+    Nothing -> withSystemTempFile "_mucheck" $ \tmpf tmph -> do+        stdout_dup <- hDuplicate stdout+        stderr_dup <- hDuplicate stderr+        hDuplicateTo tmph stdout+        hDuplicateTo tmph stderr+        hClose tmph+        res <- f+        hDuplicateTo stdout_dup stdout+        hDuplicateTo stderr_dup stderr+        str <- readFile tmpf+        return (res,str)+
src/Test/MuCheck/Utils/Syb.hs view
@@ -3,7 +3,7 @@ module Test.MuCheck.Utils.Syb (relevantOps, once) where  import Data.Generics (Data, GenericM, gmapMo)-import Test.MuCheck.MuOp (mkMp', MuOp, same)+import Test.MuCheck.MuOp (mkMpMuOp, MuOp, same) import Control.Monad (MonadPlus, mplus) import Data.Maybe(isJust) @@ -19,5 +19,5 @@ relevantOps :: (Data a, Eq a) => a -> [MuOp] -> [MuOp] relevantOps m oplst = filter (relevantOp m) $ filter (not . same) oplst   -- check if an operator can be applied to a program-  where relevantOp m' op = isJust $ once (mkMp' op) m'+  where relevantOp m' op = isJust $ once (mkMpMuOp op) m'