-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathWhiteboxTestGenerator.hs
122 lines (102 loc) · 3.57 KB
/
WhiteboxTestGenerator.hs
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
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import qualified Data.ByteString.Lazy as B
import Data.List
import GHC.Generics
import System.Environment
import Head as H
import Elixir
import Helpers
import Parser
import ConfigParser
getJSON :: FilePath -> IO B.ByteString
getJSON = B.readFile
data Graph =
Graph
{ nodes :: [Node]
, edges :: [Edge]
}
deriving (Show, Generic)
data Node =
Node
{ nodeId :: Integer
, label :: String
}
deriving (Show, Generic)
data Edge =
Edge
{ nodeFrom :: Integer
, nodeTo :: Integer
}
deriving (Show, Generic)
instance FromJSON Graph where
parseJSON (Object v) = Graph <$> v .: "objects" <*> v .: "edges"
instance FromJSON Node where
parseJSON (Object v) = Node <$> v .: "_gvid" <*> v .: "label"
instance FromJSON Edge where
parseJSON (Object v) = Edge <$> v .: "tail" <*> v .: "head"
genTests :: String -> [String] -> Graph -> Either String String
genTests m ms Graph {nodes = ns, edges = es} =
case traverse (testForNode (map ((m ++ "_") ++) ms) (Graph ns es)) ns of
Right ts -> Right (header m ++ intercalate "\n" ts ++ "\nend")
Left e -> Left e
testForNode :: [String] -> Graph -> Node -> Either String String
testForNode ms g Node {nodeId = i, label = l} = do
vs <- toMap Node {nodeId = i, label = l}
ss <- statesFromId g i >>= traverse toMap
return
(unlines
[ "test \"fromState " ++ show i ++ "\" do"
, " variables = " ++ vs
, ""
, " expectedStates = [" ++ intercalate ",\n" ss ++ "]"
, ""
, " actions = List.flatten([" ++ intercalate ", " (map (++ ".next(variables)") ms) ++ "])"
, " states = Enum.map(actions, fn action -> action[:transition].(variables) end)"
, ""
, " assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates))"
, "end"
])
statesFromId :: Graph -> Integer -> Either String [Node]
statesFromId Graph {nodes = ns, edges = es} i =
let edgesFromId = filter (\Edge {nodeFrom = f, nodeTo = _} -> f == i) es
nodesIdsFromId = map (\Edge {nodeFrom = _, nodeTo = t} -> t) edgesFromId
in traverse (findNode ns) nodesIdsFromId
findNode :: [Node] -> Integer -> Either String Node
findNode ns n =
case find (\Node {nodeId = i, label = _} -> n == i) ns of
Just node -> Right node
Nothing -> Left ("Node with id " ++ show n ++ " could not be found")
toMap :: Node -> Either String String
toMap Node {nodeId = _, label = l} =
case parseState (unescape l) of
Right a -> Right (initialState [] (toValue a))
Left e -> Left (show e)
unescape :: String -> String
unescape [] = []
unescape [s] = [s]
unescape (c1:c2:cs) =
if c1 == '\\' && (c2 == '\\' || c2 == 'n')
then (if c2 == 'n'
then unescape cs
else unescape (c2 : cs))
else c1 : unescape (c2 : cs)
header :: String -> String
header m = unlines ["defmodule " ++ m ++ "Test do", " use ExUnit.Case", " doctest " ++ m]
generateWhiteboxTests ps (Config _ _ _ _ _ name _ _ file _ dest) = do
d <- (eitherDecode <$> getJSON file) :: IO (Either String Graph)
case d of
Left err -> putStrLn err
Right graph -> case genTests name ps graph of
Left err -> putStrLn err
Right s ->
let f = dest ++ "/test/generated_code/" ++ snake name ++ "_test.exs"
in writeFile f s
main :: IO ()
main = do
(configFile:_) <- getArgs
config <- parseConfig configFile
case fmap (\c -> generateWhiteboxTests (processNames c) c) config of
Left err -> error err
Right c -> c