Merge pull request #87 from trailofbits/dev-json-output

Can now choose to output only JSON corresponding to test output
pull/91/head
Ben Perez 6 years ago committed by GitHub
commit 1b5129d1f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      lib/Echidna/Config.hs
  2. 2
      lib/Echidna/Exec.hs
  3. 217
      lib/Echidna/Internal/JsonRunner.hs
  4. 4
      perprop/Main.hs
  5. 6
      src/Main.hs

@ -31,6 +31,7 @@ data Config = Config
, _returnType :: PropertyType
, _prefix :: Text
, _printCoverage :: Bool
, _outputJson :: Bool
}
deriving Show
@ -51,6 +52,7 @@ instance FromJSON Config where
<*> v .:? "return" .!= ShouldReturnTrue
<*> v .:? "prefix" .!= "echidna_"
<*> v .:? "printCoverage" .!= False
<*> v .:? "outputJson" .!= False
parseJSON _ = parseJSON (Object mempty)
newtype ParseException = ParseException FilePath

@ -22,6 +22,7 @@ module Echidna.Exec (
, ppHashes
, printResults
, module Echidna.Internal.Runner
, module Echidna.Internal.JsonRunner
) where
import Control.Concurrent.MVar (MVar, modifyMVar_)
@ -62,6 +63,7 @@ import EVM.Types (W256)
import Echidna.ABI (SolCall, SolSignature, displayAbiCall, encodeSig, genInteractions, mutateCall)
import Echidna.Config (Config(..), testLimit, printCoverage, range, shrinkLimit)
import Echidna.Internal.Runner
import Echidna.Internal.JsonRunner
import Echidna.Property (PropertyType(..))
--------------------------------------------------------------------

@ -0,0 +1,217 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DoAndIfThenElse #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}
module Echidna.Internal.JsonRunner (
checkParallelJson
) where
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Aeson (ToJSON, encode)
import Data.ByteString.Lazy.Char8 (unpack)
import Data.Maybe (mapMaybe)
import GHC.Generics
import Hedgehog.Internal.Config
import Hedgehog.Internal.Gen (runDiscardEffect, runGenT)
import Hedgehog.Internal.Property
(Failure(..), Group(..), Property(..), PropertyT(..),
PropertyConfig(..), ShrinkLimit(..), ShrinkRetries(..),
Log(..), Diff(..), runTestT, unPropertyName)
import Hedgehog.Internal.Queue
import Hedgehog.Internal.Report
(FailedAnnotation(..), FailureReport(..), Result(..), ShrinkCount(..))
import Hedgehog.Internal.Runner (RunnerConfig(..))
import Hedgehog.Internal.Seed (Seed)
import qualified Hedgehog.Internal.Seed as Seed
import Hedgehog.Internal.Source
import Hedgehog.Internal.Tree (Node(..), Tree(..), runTree)
import Hedgehog.Range (Size)
data JsonOutput = JsonOutput {
propName :: !String
, propTrue :: !Bool
, propCall :: !(Maybe [String])
} deriving (Generic, Show)
instance ToJSON JsonOutput
checkParallelJson :: MonadIO m => Group -> m Bool
checkParallelJson =
checkGroup
RunnerConfig {
runnerWorkers =
Nothing
, runnerColor =
Nothing
, runnerVerbosity =
Nothing
}
checkGroup :: MonadIO m => RunnerConfig -> Group -> m Bool
checkGroup config (Group _ props) =
liftIO $ do
n <- resolveWorkers (runnerWorkers config)
updateNumCapabilities (n + 2)
_ <- runTasks n props st noop noop $ \(name, prop) -> do
result <- checkProp 0 prop
putStrLn $ unpack $ encode (format name result)
pure ()
pure True
where st _ _ (name,prop) = pure (name,prop)
noop = const $ pure ()
failVals (FailureReport _ _ _ xs _ _ _ _) = map (\(FailedAnnotation _ v) -> v) xs
format n r = let name = unPropertyName n in
case r of
OK -> JsonOutput { propName = name, propTrue = True, propCall = Nothing }
GaveUp -> JsonOutput { propName = name, propTrue = False, propCall = Nothing }
Failed s -> JsonOutput { propName = name, propTrue = False, propCall = Just (failVals s) }
checkProp :: Size -> Property -> IO Result
checkProp size0 (Property conf test) = Seed.random >>= loop (0 :: Integer) (0 :: Integer) size0
where loop !tests !discards !size !seed =
if size > 99 then
-- size has reached limit, reset to 0
loop tests discards 0 seed
else if tests >= fromIntegral (propertyTestLimit conf) then
-- we've hit the test limit, test was successful
pure $ OK
else if discards >= fromIntegral (propertyDiscardLimit conf) then
-- we've hit the discard limit, give up
pure $ GaveUp
else
case Seed.split seed of
(s0, s1) -> do
node@(Node x _) <-
runTree . runDiscardEffect $ runGenT size s0 . runTestT $ unPropertyT test
case x of
Nothing ->
loop tests (discards + 1) (size + 1) s1
Just (Left _, _) ->
takeSmallest
size
seed
0
(propertyShrinkLimit conf)
(propertyShrinkRetries conf)
node
Just (Right (), _) ->
loop (tests + 1) discards (size + 1) s1
takeSmallest ::
Size
-> Seed
-> ShrinkCount
-> ShrinkLimit
-> ShrinkRetries
-> Node IO (Maybe (Either Failure (), [Log]))
-> IO Result
takeSmallest size seed shrinks slimit retries = \case
Node Nothing _ ->
pure GaveUp
Node (Just (x, w)) xs ->
case x of
Left (Failure loc err mdiff) -> do
let failure = mkFailure size seed shrinks loc err mdiff (reverse w)
if shrinks >= fromIntegral slimit then
-- if we've hit the shrink limit, don't shrink any further
pure $ Failed failure
else
findM xs (Failed failure) $ \m -> do
o <- runTreeN retries m
if isFailure o then
Just <$> takeSmallest size seed (shrinks + 1) slimit retries o
else
return Nothing
Right () ->
return OK
isFailure :: Node m (Maybe (Either x a, b)) -> Bool
isFailure = \case
Node (Just (Left _, _)) _ ->
True
_ ->
False
isSuccess :: Node m (Maybe (Either x a, b)) -> Bool
isSuccess =
not . isFailure
runTreeN ::
Monad m
=> ShrinkRetries
-> Tree m (Maybe (Either x a, b))
-> m (Node m (Maybe (Either x a, b)))
runTreeN n m = do
o <- runTree m
if n > 0 && isSuccess o then
runTreeN (n - 1) m
else
pure o
findM :: Monad m => [a] -> b -> (a -> m (Maybe b)) -> m b
findM xs0 def p =
case xs0 of
[] ->
return def
x0 : xs ->
p x0 >>= \m ->
case m of
Nothing ->
findM xs def p
Just x ->
return x
takeAnnotation :: Log -> Maybe FailedAnnotation
takeAnnotation = \case
Annotation loc val ->
Just $ FailedAnnotation loc val
_ ->
Nothing
takeFootnote :: Log -> Maybe String
takeFootnote = \case
Footnote x ->
Just x
_ ->
Nothing
mkFailure ::
Size
-> Seed
-> ShrinkCount
-> Maybe Span
-> String
-> Maybe Diff
-> [Log]
-> FailureReport
mkFailure size seed shrinks location message diff logs =
let
inputs =
mapMaybe takeAnnotation logs
footnotes =
mapMaybe takeFootnote logs
in
FailureReport size seed shrinks inputs location message diff footnotes

@ -74,7 +74,7 @@ readConf :: FilePath -> IO (Maybe (Config, [Property]))
readConf f = decodeEither <$> BS.readFile f >>= \case
Left e -> putStrLn ("couldn't parse config, " ++ e) >> pure Nothing
Right (PerPropConf t s p) -> pure . Just . (,p) $
defaultConfig & addrList .~ Just (view address <$> s) & testLimit .~ t & epochs .~ 1
defaultConfig & addrList .~ Just (view address <$> s) & testLimit .~ t & epochs .~ 1 & outputJson .~ True
group :: String
-> Config
@ -106,7 +106,7 @@ main = getArgs >>= \case
_ <- swapMVar mvar []
pure (p,lastGen,mvar)
checkParallel $ group sf c a v xs
checkParallelJson $ group sf c a v xs
ls <- mapM (readMVar . snd) tests
let ci = foldl' (\acc xs -> unions (acc : map snd xs)) mempty ls

@ -55,7 +55,9 @@ main = do
-- Read cmd line options and load config
(Options file contract usecov configFile) <- execParser opts
config <- maybe (pure defaultConfig) parseConfig configFile
let f = checkTest (config ^. returnType)
checkGroup = if config ^. outputJson then checkParallelJson else checkParallel
flip runReaderT config $ do
-- Load solidity contract and get VM
@ -64,7 +66,7 @@ main = do
-- Run without coverage
then do
let prop t = ePropertySeq (`f` t) a v >>= \x -> return (PropertyName $ show t, x)
_ <- checkParallel . Group (GroupName file) =<< mapM prop ts
_ <- checkGroup . Group (GroupName file) =<< mapM prop ts
return ()
-- Run with coverage
@ -80,7 +82,7 @@ main = do
_ <- swapMVar y []
return (lastGen,x,y)
checkParallel . Group (GroupName file) =<< mapM prop xs
checkGroup . Group (GroupName file) =<< mapM prop xs
ls <- liftIO $ mapM (readMVar . snd) tests
let ci = foldl' (\acc xs -> unions (acc : map snd xs)) mempty ls

Loading…
Cancel
Save