Skip to content

Commit d7c7786

Browse files
shrink when a test was interrupted (#1535)
1 parent 1cc7120 commit d7c7786

File tree

3 files changed

+25
-4
lines changed

3 files changed

+25
-4
lines changed

lib/Echidna/Campaign.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,14 +391,14 @@ runFuzzWorker callback vm dict workerId initialCorpus testLimit = do
391391
| (null tests || any isOpen tests) && ncalls < testLimit ->
392392
fuzz >> lift callback >> run
393393

394-
-- NOTE: this is a hack which forces shrinking of optimization tests
395-
-- after test limit is reached
394+
-- Test limit reached. Close any open optimization tests so they
395+
-- enter the shrink loop above, same as other test types.
396396
| ncalls >= testLimit && any (\t -> isOpen t && isOptimizationTest t) tests -> do
397397
liftIO $ forM_ testRefs $ \testRef ->
398398
atomicModifyIORef' testRef (\test -> (closeOptimizationTest test, ()))
399399
lift callback >> run
400400

401-
-- no more work to do, means we reached the test limit, exit
401+
-- no more work to do, exit
402402
| otherwise ->
403403
lift callback >> pure TestLimitReached
404404

lib/Echidna/Types/Test.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ isOpen t = case t.state of
138138
Open -> True
139139
_ -> False
140140

141+
-- | Whether a test still needs shrinking. This includes optimization tests
142+
-- that haven't been closed yet (Open) and any test mid-shrink (Large).
143+
needsShrinking :: EchidnaTest -> Bool
144+
needsShrinking t =
145+
case t.state of
146+
Large _ -> True
147+
Open -> isOptimizationTest t
148+
_ -> False
149+
141150
didFail :: EchidnaTest -> Bool
142151
didFail t = case t.state of
143152
Large _ -> True

lib/Echidna/UI.hs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import Echidna.Types.Campaign
4242
import Echidna.Types.Config
4343
import Echidna.Types.Corpus qualified as Corpus
4444
import Echidna.Types.Coverage (coverageStats)
45-
import Echidna.Types.Test (EchidnaTest(..), TestState(..), didFail, isOptimizationTest)
45+
import Echidna.Types.Test (EchidnaTest(..), TestState(..), didFail, isOptimizationTest, needsShrinking)
4646
import Echidna.Types.Tx (Tx)
4747
import Echidna.Types.Worker
4848
import Echidna.UI.Report
@@ -248,6 +248,18 @@ ui vm dict initialCorpus cliSelectedContract = do
248248
, Handler $ \(e :: SomeException) -> pure $ Crashed (show e)
249249
]
250250

251+
-- When a fuzz worker is interrupted by timeout, tests may not have
252+
-- finished shrinking. Run a shrink-only pass outside the timeout using
253+
-- the same worker loop (testLimit=0 means no fuzzing, only shrink).
254+
-- (See github.com/crytic/echidna/issues/839)
255+
case stopReason of
256+
TimeLimitReached | workerType == FuzzWorker -> do
257+
tests <- traverse readIORef env.testRefs
258+
when (any needsShrinking tests) $ void $
259+
runReaderT (runWorker FuzzWorker (get >>= writeIORef stateRef)
260+
vm dict workerId [] 0 cliSelectedContract) env
261+
_ -> pure ()
262+
251263
time <- liftIO getTimestamp
252264
writeChan env.eventQueue (time, WorkerEvent workerId workerType (WorkerStopped stopReason))
253265

0 commit comments

Comments
 (0)