Skip to content

Commit bd73fab

Browse files
authored
Merge pull request #804 from tsoding/801
(#801) Add Exprs fuzzer
2 parents 43889e7 + fee3413 commit bd73fab

File tree

4 files changed

+165
-6
lines changed

4 files changed

+165
-6
lines changed

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: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import Control.Applicative
77
import Data.Char
88
import qualified Data.Text as T
99
import Data.Tuple
10-
import Effect
1110

1211
data Expr
1312
= TextExpr T.Text
@@ -65,14 +64,16 @@ sepBy element sep = do
6564
args <- many (sep >> element)
6665
return (arg : args)
6766

67+
funcallarg :: Parser Expr
68+
funcallarg = funcall <|> var <|> stringLiteral
69+
6870
funcall :: Parser Expr
6971
funcall = do
7072
_ <- charP '%' >> whitespaces
7173
name <- symbol
7274
_ <- whitespaces >> charP '(' >> whitespaces
7375
args <-
74-
sepBy (funcall <|> var <|> stringLiteral) (whitespaces >> charP ',') <|>
75-
return []
76+
sepBy funcallarg (whitespaces >> charP ',' >> whitespaces) <|> return []
7677
_ <- whitespaces >> charP ')'
7778
return $ FunCallExpr name args
7879

@@ -117,7 +118,6 @@ expr = funcall <|> var <|> textBlock
117118

118119
exprs :: Parser [Expr]
119120
exprs = many expr
120-
121121
-- TODO(#600): interpretExprs is not implemented
122-
interpretExprs :: NameTable -> [Expr] -> Effect T.Text
123-
interpretExprs = undefined
122+
-- interpretExprs :: NameTable -> [Expr] -> Effect T.Text
123+
-- interpretExprs = undefined

src/FuzzyMain.hs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
3+
module Main where
4+
5+
import Bot.Expr
6+
import Control.Monad
7+
import Data.Aeson
8+
import Data.Aeson.Types
9+
import Data.Char
10+
import Data.List
11+
import qualified Data.Text as T
12+
import System.Environment
13+
import System.Random
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 report
132+
printf "Successes: %d\n" $ length $ filter id report
133+
134+
mainWithArgs :: [String] -> IO ()
135+
mainWithArgs (fuzzParamsPath:_) = readFuzzParams fuzzParamsPath >>= fuzz
136+
mainWithArgs _ = error "Usage: Fuzz <fuzz.json>"
137+
138+
main :: IO ()
139+
main = getArgs >>= mainWithArgs

test/Bot/ExprTest.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ exprsTest =
5252
, TextExpr " baz ()"
5353
]))
5454
, ("%f(%x)", Right ("", [FunCallExpr "f" [VarExpr "x"]]))
55+
, ("%f(%x, %y)", Right ("", [FunCallExpr "f" [VarExpr "x", VarExpr "y"]]))
5556
, ( "\"hello %x world\""
5657
, Right ("", [TextExpr "\"hello ", VarExpr "x", TextExpr " world\""]))
5758
]

0 commit comments

Comments
 (0)