Skip to content

Commit de13068

Browse files
committed
(#801) Add Exprs fuzzer
1 parent 1b8711d commit de13068

3 files changed

Lines changed: 162 additions & 3 deletions

File tree

HyperNerd.cabal

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,25 @@ executable HyperNerd
151151
-- Base language which the package is written in.
152152
default-language: Haskell2010
153153

154+
executable Fuzzy
155+
ghc-options: -threaded
156+
-Wall
157+
-fwarn-incomplete-patterns
158+
-fwarn-incomplete-uni-patterns
159+
160+
main-is: FuzzyMain.hs
161+
162+
other-modules: Bot.Expr
163+
164+
build-depends: base
165+
, text
166+
, random
167+
, aeson
168+
169+
hs-source-dirs: src
170+
171+
default-language: Haskell2010
172+
154173
executable Markov
155174
ghc-options: -threaded
156175
-Wall

src/Bot/Expr.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Control.Applicative
77
import Data.Char
88
import qualified Data.Text as T
99
import Data.Tuple
10-
import Effect
10+
-- import Effect
1111

1212
data Expr
1313
= TextExpr T.Text
@@ -121,5 +121,5 @@ exprs :: Parser [Expr]
121121
exprs = many expr
122122

123123
-- TODO(#600): interpretExprs is not implemented
124-
interpretExprs :: NameTable -> [Expr] -> Effect T.Text
125-
interpretExprs = undefined
124+
-- interpretExprs :: NameTable -> [Expr] -> Effect T.Text
125+
-- interpretExprs = undefined

src/FuzzyMain.hs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
3+
module Main where
4+
5+
import qualified Data.Text as T
6+
import Bot.Expr
7+
import System.Random
8+
import Control.Monad
9+
import Data.Char
10+
import Data.List
11+
import Data.Aeson.Types
12+
import Data.Aeson
13+
import System.Environment
14+
import Text.Printf
15+
16+
data FuzzParams = FuzzParams
17+
{ fpFuzzCount :: Int
18+
, fpExprsRange :: (Int, Int)
19+
, fpFunCallArgsRange :: (Int, Int)
20+
, fpWordLenRange :: (Int, Int)
21+
, fpTextWordCountRange :: (Int, Int)
22+
} deriving (Show, Eq)
23+
24+
instance ToJSON FuzzParams where
25+
toJSON params =
26+
object
27+
[ "FuzzCount" .= fpFuzzCount params
28+
, "ExprsRange" .= fpExprsRange params
29+
, "FunCallArgsRange" .= fpFunCallArgsRange params
30+
, "WordLenRange" .= fpWordLenRange params
31+
, "TextWordCountRange" .= fpTextWordCountRange params
32+
]
33+
34+
instance FromJSON FuzzParams where
35+
parseJSON (Object params) =
36+
FuzzParams <$> params .: "FuzzCount" <*> params .: "ExprsRange" <*>
37+
params .: "FunCallArgsRange" <*>
38+
params .: "WordLenRange" <*>
39+
params .: "TextWordCountRange"
40+
parseJSON invalid = typeMismatch "FuzzParams" invalid
41+
42+
readFuzzParams :: FilePath -> IO FuzzParams
43+
readFuzzParams = fmap (either error id) . eitherDecodeFileStrict
44+
45+
defaultFuzzParams :: FuzzParams
46+
defaultFuzzParams =
47+
FuzzParams
48+
{ fpFuzzCount = 100
49+
, fpExprsRange = (1, 100)
50+
, fpFunCallArgsRange = (0, 2)
51+
, fpWordLenRange = (2, 10)
52+
, fpTextWordCountRange = (3, 5)
53+
}
54+
55+
unparseFunCallArg :: Expr -> T.Text
56+
unparseFunCallArg (TextExpr text) = "\"" <> text <> "\""
57+
unparseFunCallArg e = unparseExpr e
58+
59+
unparseFunCallArgs :: [Expr] -> T.Text
60+
unparseFunCallArgs = T.concat . intersperse "," . map unparseFunCallArg
61+
62+
unparseExpr :: Expr -> T.Text
63+
unparseExpr (TextExpr text) = text
64+
unparseExpr (VarExpr name) = "%" <> name
65+
unparseExpr (FunCallExpr name args) =
66+
"%" <> name <> "(" <> unparseFunCallArgs args <> ")"
67+
68+
unparseExprs :: [Expr] -> T.Text
69+
unparseExprs = T.concat . map unparseExpr
70+
71+
randomChar :: IO Char
72+
randomChar = do
73+
x <- randomRIO (0, ord 'z' - ord 'a')
74+
return $ chr (x + ord 'a')
75+
76+
randomText :: FuzzParams -> IO T.Text
77+
randomText params = do
78+
n <- randomRIO $ fpTextWordCountRange params
79+
T.concat . intersperse " " <$> replicateM n (randomWord params)
80+
81+
randomWord :: FuzzParams -> IO T.Text
82+
randomWord params = do
83+
n <- randomRIO $ fpWordLenRange params
84+
T.pack <$> replicateM n randomChar
85+
86+
randomTextExpr :: FuzzParams -> IO Expr
87+
randomTextExpr params = TextExpr <$> randomText params
88+
89+
randomVarExpr :: FuzzParams -> IO Expr
90+
randomVarExpr params = VarExpr <$> randomWord params
91+
92+
randomFunCallExpr :: FuzzParams -> IO Expr
93+
randomFunCallExpr params = do
94+
name <- randomWord params
95+
n <- randomRIO $ fpFunCallArgsRange params
96+
args <- replicateM n (randomExpr params)
97+
return $ FunCallExpr name args
98+
99+
randomExpr :: FuzzParams -> IO Expr
100+
randomExpr params = do
101+
n <- (randomRIO (0, 2) :: IO Int)
102+
case n of
103+
0 -> randomTextExpr params
104+
1 -> randomVarExpr params
105+
_ -> randomFunCallExpr params
106+
107+
normalizeExprs :: [Expr] -> [Expr]
108+
normalizeExprs [] = []
109+
normalizeExprs (TextExpr t1:TextExpr t2:rest) =
110+
normalizeExprs (TextExpr (t1 <> t2):rest)
111+
normalizeExprs (_:rest) = normalizeExprs rest
112+
113+
randomExprs :: FuzzParams -> IO [Expr]
114+
randomExprs params = do
115+
n <- randomRIO $ fpExprsRange params
116+
replicateM n (randomExpr params)
117+
118+
fuzzIteration :: FuzzParams -> IO Bool
119+
fuzzIteration params = do
120+
es <- normalizeExprs <$> randomExprs params
121+
let es' = runParser exprs $ unparseExprs es
122+
when ((Right ("", es)) /= es') $ do
123+
print es
124+
print es'
125+
error "test"
126+
return ((Right ("", es)) == es')
127+
128+
fuzz :: FuzzParams -> IO ()
129+
fuzz params = do
130+
report <- replicateM (fpFuzzCount params) (fuzzIteration params)
131+
printf "Failures: %d\n" $ length $ filter (not . id) report
132+
printf "Successes: %d\n" $ length $ filter id report
133+
134+
mainWithArgs :: [String] -> IO ()
135+
mainWithArgs (fuzzParamsPath:_) = do
136+
readFuzzParams fuzzParamsPath >>= fuzz
137+
mainWithArgs _ = error "Usage: Fuzz <fuzz.json>"
138+
139+
main :: IO ()
140+
main = getArgs >>= mainWithArgs

0 commit comments

Comments
 (0)