Skip to content

Commit a8eed18

Browse files
committed
Add toggle for lazy pruning mutation order
1 parent 5e68065 commit a8eed18

5 files changed

Lines changed: 77 additions & 30 deletions

File tree

src/Test/Mutagen/Config.hs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ module Test.Mutagen.Config
1515
, deny
1616
, deny'
1717
, example
18+
, LazyPruningMode (..)
19+
, EvaluationOrder (..)
1820
, DebugMode (..)
1921

2022
-- * Re-exports
@@ -62,11 +64,11 @@ data Config
6264
-- certain number of tests. If not set to `Nothing`, this will duplicate the
6365
-- current limit on every reset. Additionally, it also duplicates the
6466
-- `randomMutations` parameter.
65-
, useLazyPrunning :: Bool
66-
-- ^ Use lazy prunning to avoid mutating unevaluated subexpressions. The
67+
, lazyPruning :: LazyPruningMode
68+
-- ^ Use lazy pruning to avoid mutating unevaluated subexpressions. The
6769
-- target mutable subexpressions are ordered by last evaluated first.
6870
, mutationOrder :: MutationOrder
69-
-- ^ If `useLazyPrunning` is set to `False`, *every* subexpression of an
71+
-- ^ If `lazyPruning` is set to `False`, *every* subexpression of an
7072
-- interesting test case is mutated regardless whether it was evaluated or
7173
-- not. These subexpressions are ordered using a generic tree traversal order
7274
-- (level order by default). The provided options are:
@@ -130,7 +132,7 @@ defaultConfig =
130132
, randomMutations = 1
131133
, maxMutationDepth = Nothing
132134
, autoResetAfter = Just 1000
133-
, useLazyPrunning = False
135+
, lazyPruning = LazyPruning Forward
134136
, mutationOrder = levelorder
135137
, useFragments = False
136138
, randomFragments = 10
@@ -169,6 +171,28 @@ deny' _ = deny @a
169171
example :: forall a. (IsArgs a) => a -> Args
170172
example = Args
171173

174+
-- | Lazy pruning mode.
175+
--
176+
-- Used to dictate whether lazy pruning is used or not, and in which order
177+
-- subexpressions are mutated.
178+
data LazyPruningMode
179+
= -- | Do not use lazy pruning; mutate all subexpressions.
180+
NoLazyPruning
181+
| -- | Use lazy pruning; mutate only evaluated subexpressions, following the
182+
-- order in which they were evaluated.
183+
LazyPruning EvaluationOrder
184+
deriving (Eq, Show)
185+
186+
-- | Evaluation order for lazy pruning.
187+
--
188+
-- Used to dictate how to order the evaluated subexpressions to be mutated.
189+
data EvaluationOrder
190+
= -- | Mutate the least recently evaluated subexpressions first.
191+
Forward
192+
| -- | Mutate the most recently evaluated subexpressions first.
193+
Backward
194+
deriving (Eq, Show)
195+
172196
-- | Debugging mode.
173197
--
174198
-- Allows stopping the loop between test cases to inspect the internal state.

src/Test/Mutagen/Test/Loop.hs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import Control.Monad (void, when)
1616
import Control.Monad.IO.Class (MonadIO (..))
1717
import Data.Function ((&))
1818
import System.Random (split)
19-
import Test.Mutagen.Config (DebugMode (..))
19+
import Test.Mutagen.Config
20+
( DebugMode (..)
21+
, EvaluationOrder (..)
22+
, LazyPruningMode (..)
23+
)
2024
import Test.Mutagen.Fragment.Store (storeFragments)
2125
import Test.Mutagen.Lazy (withLazyIO)
2226
import Test.Mutagen.Mutation (Pos)
@@ -376,8 +380,6 @@ runTestCase args parent st = do
376380
message "Running test case..."
377381
-- Run the test case
378382
(result, trace, evaluatedPos) <- liftIO $ execPropRunner st args
379-
-- Truncate the trace if necessary
380-
let trace' = maybe trace (flip truncateTrace trace) (stMaxTraceLength st)
381383
when (stChatty st) $ do
382384
case evaluatedPos of
383385
Just pos -> do
@@ -387,7 +389,7 @@ runTestCase args parent st = do
387389
return () -- lazy prunning is disabled
388390
-- Print the trace of the mutated test case
389391
message "Test case trace:"
390-
pretty (unTrace trace')
392+
pretty (unTrace trace)
391393
-- Extract property modifiers
392394
let addPropertyModifiers =
393395
setExpect (resultExpect result)
@@ -407,7 +409,7 @@ runTestCase args parent st = do
407409
when (stChatty st) $ do
408410
message "Test result: PASSED"
409411
-- Save the trace in the corresponding trace store
410-
(new, prio) <- liftIO (savePassedTraceWithPrio st trace')
412+
(new, prio) <- liftIO (savePassedTraceWithPrio st trace)
411413
-- Evaluate whether the test case was interesting or not depending on
412414
-- whether it added new trace nodes or not
413415
let interesting = new > 0
@@ -442,7 +444,7 @@ runTestCase args parent st = do
442444
when (stChatty st) $ do
443445
message "Test result: DISCARDED"
444446
-- Save the trace in the corresponding trace log
445-
(new, prio) <- liftIO (saveDiscardedTraceWithPrio st trace')
447+
(new, prio) <- liftIO (saveDiscardedTraceWithPrio st trace)
446448
-- Evaluate whether the test case was interesting or not
447449
--
448450
-- NOTE: in this case, we only consider discarded test cases interesting
@@ -519,19 +521,33 @@ runTestCase args parent st = do
519521

520522
-- | Execute a test and capture:
521523
-- * The test result (passed, discarded, failed)
522-
-- * The trace in the program it traversed
523-
-- * The positions of the evaluated expressions of the input
524-
-- (if lazy pruning is currently enabled).
524+
-- * The (possibly truncated) execution trace in the program it traversed
525+
-- * The positions of the evaluated subexpressions of the input, in the order
526+
-- that they need to be mutated (only when lazy pruning is enabled).
525527
execPropRunner :: MutagenState -> Args -> IO (Result, Trace, Maybe [Pos])
526528
execPropRunner st args
527-
| stUseLazyPrunning st = do
528-
(evaluated, (test, trace)) <- withLazyIO (withTrace . runProp) args
529-
return (test, trace, Just evaluated)
529+
| LazyPruning order <- stLazyPruning st = do
530+
(evaluated, (result, trace)) <- withLazyIO (withTrace . runProp) args
531+
return
532+
( result
533+
, truncateTraceIfNeeded trace
534+
, Just (withMutationOrder order evaluated)
535+
)
530536
| otherwise = do
531-
(test, trace) <- withTrace (runProp args)
532-
return (test, trace, Nothing)
537+
(result, trace) <- withTrace (runProp args)
538+
return
539+
( result
540+
, truncateTraceIfNeeded trace
541+
, Nothing
542+
)
533543
where
534544
runProp = unProp . protectProp . stPropRunner st
545+
withMutationOrder order =
546+
case order of
547+
Forward -> id
548+
Backward -> reverse
549+
truncateTraceIfNeeded trace =
550+
maybe trace (flip truncateTrace trace) (stMaxTraceLength st)
535551

536552
-- | Save a discarded trace and return the number of new nodes added and its
537553
-- the priority associated to its corresponding test case.

src/Test/Mutagen/Test/Queue.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ newMutationBatchFromParent
171171
-> MutationBatch a
172172
newMutationBatchFromParent
173173
batch
174-
evaluatedPositions
174+
evaluatedPos
175175
testPassed
176176
args =
177177
batch
@@ -186,7 +186,7 @@ newMutationBatchFromParent
186186
-- Determine next positions to mutate based on whether we receive the
187187
-- concrete execution trace of the original
188188
nextPositions =
189-
fromMaybe (mbMutationOrder batch (positions args)) evaluatedPositions
189+
fromMaybe (mbMutationOrder batch (positions args)) evaluatedPos
190190

191191
-- | Create or inherit mutation batch for a test case.
192192
--
@@ -221,14 +221,14 @@ createOrInheritMutationBatch
221221
maxMutationDepth
222222
args
223223
parentBatch
224-
pos
224+
evaluatedPos
225225
isPassed =
226226
case parentBatch of
227227
-- The test case was mutated from an existing one
228228
Just mb ->
229229
newMutationBatchFromParent
230230
mb
231-
pos
231+
evaluatedPos
232232
isPassed
233233
args
234234
-- The test case was freshly generated
@@ -239,7 +239,7 @@ createOrInheritMutationBatch
239239
maxGenSize
240240
randomFragments
241241
maxMutationDepth
242-
pos
242+
evaluatedPos
243243
isPassed
244244
args
245245

src/Test/Mutagen/Test/State.hs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ where
4444
import Data.List (elemIndex)
4545
import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)
4646
import System.FilePath ((<.>))
47-
import Test.Mutagen.Config (Config (..), DebugMode (..))
47+
import Test.Mutagen.Config
48+
( Config (..)
49+
, DebugMode (..)
50+
, LazyPruningMode (..)
51+
)
4852
import Test.Mutagen.Fragment.Store
4953
( FragmentStore
5054
, FragmentTypeFilter
@@ -89,8 +93,8 @@ data MutagenState
8993
-- ^ Mirrored from 'Config.maxMutationDepth'
9094
, stAutoResetAfter :: !(Maybe Int)
9195
-- ^ Mirrored from 'Config.autoResetAfter'
92-
, stUseLazyPrunning :: !Bool
93-
-- ^ Mirrored from 'Config.useLazyPrunning'
96+
, stLazyPruning :: !(LazyPruningMode)
97+
-- ^ Mirrored from 'Config.lazyPruning'
9498
, stMutationOrder :: !MutationOrder
9599
-- ^ Mirrored from 'Config.mutationOrder'
96100
, stRandomFragments :: !Int
@@ -194,7 +198,7 @@ initMutagenState cfg (Property gen runner) = do
194198
, stRandomFragments = randomFragments cfg
195199
, stMaxMutationDepth = maybe (maxGenSize cfg) id (maxMutationDepth cfg)
196200
, stAutoResetAfter = autoResetAfter cfg
197-
, stUseLazyPrunning = useLazyPrunning cfg
201+
, stLazyPruning = lazyPruning cfg
198202
, stMutationOrder = mutationOrder cfg
199203
, stUseFragments = useFragments cfg
200204
, stFilterFragments = filterFragments cfg

test/re/Main.hs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import System.Exit
1818
import Test.Mutagen
1919
( Config (..)
2020
, DebugMode (..)
21+
, EvaluationOrder (..)
22+
, LazyPruningMode (..)
2123
, Prop
2224
, allow
2325
, defaultConfig
@@ -53,9 +55,10 @@ config =
5355
, maxGenSize =
5456
-- Max generation size
5557
5
56-
, useLazyPrunning =
57-
-- Prune mutations affecting unevaluated subexpressions
58-
True
58+
, lazyPruning =
59+
-- Prune mutations affecting unevaluated subexpressions, following the
60+
-- same evaluation order of the property over the original test case
61+
LazyPruning Forward
5962
, useFragments =
6063
-- Keep a store of test case fragments to be reused
6164
True

0 commit comments

Comments
 (0)