-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathFuzz.hs
More file actions
192 lines (168 loc) · 5.15 KB
/
Fuzz.hs
File metadata and controls
192 lines (168 loc) · 5.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Fuzz testing for math programming backends.
module Math.Programming.Tests.Fuzz where
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.State
import Control.Monad.Writer
import qualified Data.Map as M
import qualified Data.Sequence as S
import qualified Data.Text as T
import Lens.Micro
import Lens.Micro.Mtl
import Lens.Micro.TH
import Math.Programming
import System.Random
import System.Random.Stateful
import Test.Hspec hiding (focus, pending)
import Test.Hspec.QuickCheck
import Test.QuickCheck
newtype Variable = Variable Int
deriving
( Show,
Eq,
Ord
)
newtype Constraint = Constraint Int
deriving
( Show,
Eq,
Ord
)
newtype Objective = Objective Int
deriving
( Show,
Eq,
Ord
)
-- | The types of actions we can perform on a linear program
data LPAction
= AddVariable Variable
| AddThenRemoveVariable Variable
| AddConstraint Constraint
| AddThenRemoveConstraint Constraint
| AddObjective Objective
| AddThenRemoveObjective Objective
| Optimize
deriving (Show)
newtype LPActions = LPActions [LPAction]
deriving (Show)
instance Arbitrary LPActions where
arbitrary = do
NonNegative actionCount <- arbitrary
actions <- forM [1 .. actionCount] $ \i -> do
d7 <- fmap (`mod` (7 :: Int)) arbitrary
pure $ case d7 of
0 -> AddVariable (Variable i)
1 -> AddThenRemoveVariable (Variable i)
2 -> AddConstraint (Constraint i)
3 -> AddThenRemoveConstraint (Constraint i)
4 -> AddObjective (Objective i)
5 -> AddThenRemoveObjective (Objective i)
_ -> Optimize
pure (LPActions actions)
data LPState v c o = LPState
{ _variables :: M.Map Variable v,
_variableNames :: M.Map Variable T.Text,
_constraints :: M.Map Constraint c,
_constraintNames :: M.Map Constraint T.Text,
_objectives :: M.Map Objective o,
_objectiveNames :: M.Map Objective T.Text,
_pending :: [LPAction],
_randomGen :: IOGenM StdGen
}
makeLenses ''LPState
initLPState :: Int -> [LPAction] -> IO (LPState v c o)
initLPState seed todo = do
g <- newIOGenM (mkStdGen seed)
pure
LPState
{ _variables = M.empty,
_variableNames = M.empty,
_constraints = M.empty,
_constraintNames = M.empty,
_objectives = M.empty,
_objectiveNames = M.empty,
_pending = todo,
_randomGen = g
}
type LPFuzz v c o m =
( MonadState (LPState v c o) m,
MonadLP v c o m,
MonadWriter (S.Seq String) m,
MonadIO m
)
evalPending :: (LPFuzz v c o m) => m ()
evalPending = do
todo <- use pending
case todo of
[] -> pure ()
(x : xs) -> do
assign pending xs
evalAction x
evalPending
evalAction :: (LPFuzz v c o m) => LPAction -> m ()
evalAction action = tell (S.singleton (show action)) >> evalAction' action
evalAction' :: (LPFuzz v c o m) => LPAction -> m ()
evalAction' (AddVariable k) = add k addVariable variables
evalAction' (AddThenRemoveVariable k) = addThenRemove k addVariable deleteVariable variables
evalAction' (AddConstraint k) = add k makeConstraint constraints
evalAction' (AddThenRemoveConstraint k) = addThenRemove k makeConstraint deleteConstraint constraints
evalAction' (AddObjective k) = add k makeObjective objectives
evalAction' (AddThenRemoveObjective k) = addThenRemove k makeObjective deleteObjective objectives
evalAction' Optimize = void optimizeLP
add :: (LPFuzz v c o m, Ord k) => k -> m a -> ASetter' (LPState v c o) (M.Map k a) -> m ()
add k create focus =
create >>= modifying focus . M.insert k
addThenRemove :: (LPFuzz v c o m, Ord k) => k -> (m a) -> (a -> m ()) -> Lens' (LPState v c o) (M.Map k a) -> m ()
addThenRemove k create destroy focus = do
collection <- use focus
case M.lookup k collection of
Just v -> destroy v >> modifying focus (M.delete k)
Nothing -> add k create focus
makeConstraint :: (LPFuzz v c o m) => m c
makeConstraint = do
lhs <- chooseExpr
rhs <- chooseExpr
op <- chooseInequality
lhs `op` rhs
chooseExpr :: (LPFuzz v c o m) => m (Expr v)
chooseExpr = do
g <- use randomGen
vs <- use variables
terms <- forM (M.elems vs) $ \v -> do
c <- liftIO (uniformRM (-1e10, 1e10) g)
pure (c *. v)
pure (esum terms)
chooseInequality :: (LPFuzz v c o m) => m (Expr v -> Expr v -> m c)
chooseInequality = do
g <- use randomGen
d3 <- liftIO (uniformRM (0 :: Int, 2) g)
case d3 of
0 -> pure (.<=.)
1 -> pure (.>=.)
_ -> pure (.==.)
makeObjective :: (LPFuzz v c o m) => m o
makeObjective = do
g <- use randomGen
minimizing <- liftIO (uniformM g)
if minimizing
then chooseExpr >>= minimize
else chooseExpr >>= maximize
makeFuzzTests ::
(MonadIO m, MonadLP v c o m) =>
-- | The runner for the API being tested.
(m (S.Seq String) -> IO ()) ->
-- | The resulting test suite.
Spec
makeFuzzTests runner =
describe "Fuzz testing" $ do
prop "finds no failures" $ \seed (LPActions todo) -> do
initState <- liftIO (initLPState seed todo)
runner
. execWriterT
. flip evalStateT initState
$ evalPending