-
-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy pathGen.hs
394 lines (314 loc) · 10.1 KB
/
Gen.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
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
-- | This module contains all generators for @tomland@ testing.
module Test.Toml.Gen
( -- * Generators
-- ** Primitive
genBool
, genInt
, genInteger
, genDouble
, genWord
, genWord8
, genNatural
, genFloat
, genList
, genSmallList
, genNonEmpty
, genSet
, genHashSet
, genIntSet
, genMap
, genText
, genString
, genByteString
, genLByteString
, genLText
-- ** Dates
, genDay
, genTimeOfDay
, genLocalTime
, genZonedTime
-- ** @TOML@ specific
, genVal
, genKey
, genPrefixMap
, genToml
-- ** Other
, range100
) where
import Control.Applicative (liftA2)
import Control.Monad (forM, replicateM)
import Data.ByteString (ByteString)
import Data.Fixed (Fixed (..))
import Data.Hashable (Hashable)
import Data.HashMap.Strict (HashMap)
import Data.HashSet (HashSet)
import Data.IntSet (IntSet)
import Data.List.NonEmpty (NonEmpty)
import Data.Map.Strict (Map)
import Data.Set (Set)
import Data.Text (Text)
import Data.Time (Day, LocalTime (..), TimeOfDay (..), ZonedTime (..), fromGregorian,
minutesToTimeZone)
import Data.Word (Word8)
import GHC.Exts (fromList)
import Hedgehog (Gen, Range)
import Numeric.Natural (Natural)
import Toml.Type.AnyValue (AnyValue (..), toMArray)
import Toml.Type.Key (pattern (:||), Key (..), Piece (..))
import Toml.Type.PrefixTree (PrefixMap, PrefixTree (..))
import Toml.Type.TOML (TOML (..))
import Toml.Type.Value (TValue (..), Value (..))
import qualified Data.ByteString.Lazy as LB
import qualified Data.Char as Char
import qualified Data.List.NonEmpty as NE
import qualified Data.Map.Strict as Map
import qualified Data.Text as Text
import qualified Data.Text.Lazy as L
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import qualified Toml.Type.PrefixTree as Toml (fromList)
----------------------------------------------------------------------------
-- Common generators
----------------------------------------------------------------------------
-- @TOML@ specific
type V = Int
genVal :: Gen V
genVal = Gen.int (Range.constant 0 256)
-- | Generates random value of 'AnyValue' type.
genAnyValue :: Gen AnyValue
genAnyValue = Gen.choice $
(AnyValue <$> genArray) : noneArrayList
-- | Generate either a bare piece, or a quoted piece
genPiece :: Gen Piece
genPiece = Piece <$> Gen.choice [bare, quoted]
where
bare :: Gen Text
bare = liftA2 Text.cons Gen.alpha $ Gen.text (Range.constant 1 10) alphadashes
alphadashes :: Gen Char
alphadashes = Gen.choice [Gen.alphaNum, Gen.element "_-"]
quoted :: Gen Text
quoted = genNotEscape $ Gen.choice [quotedWith '"', quotedWith '\'']
quotedWith :: Char -> Gen Text
quotedWith c = wrapChar c <$> Gen.text (Range.constant 1 10) notControl
where
notControl :: Gen Char
notControl = Gen.filter (\x -> x /= c && not (Char.isControl x)) Gen.unicode
wrapChar :: Char -> Text -> Text
wrapChar c = Text.cons c . (`Text.append` Text.singleton c)
genKey :: Gen Key
genKey = Key <$> Gen.nonEmpty (Range.constant 1 10) genPiece
genKeyAnyValue :: Gen (Key, AnyValue)
genKeyAnyValue = liftA2 (,) genKey genAnyValue
genKeyAnyValueList :: Gen [(Key, AnyValue)]
genKeyAnyValueList = Gen.list (Range.linear 0 10) genKeyAnyValue
-- Generates key-value pair for PrefixMap
genEntry :: Gen (Piece, Key)
genEntry = genKey >>= \case
key@(piece :|| _) -> pure (piece, key)
genPrefixMap :: Gen (PrefixMap V)
genPrefixMap = do
entries <- Gen.list (Range.linear 0 10) genEntry
kvps <- forM entries $ \(piece, key) -> do
tree <- genPrefixTree key
pure (piece, tree)
pure $ fromList kvps
genPrefixTree :: Key -> Gen (PrefixTree V)
genPrefixTree key = Gen.recursive
-- list picker generator combinator
Gen.choice
-- non-recursive generators
[ Leaf key <$> genVal ]
-- recursive generators
[ genPrefixMap >>= genBranch ]
where
genBranch :: PrefixMap V -> Gen (PrefixTree V)
genBranch prefMap = do
prefVal <- Gen.maybe genVal
pure $ Branch key prefVal prefMap
makeToml :: [(Key, AnyValue)] -> TOML
makeToml kv = TOML (fromList kv) mempty mempty
genToml :: Gen TOML
genToml = Gen.recursive
Gen.choice
[ makeToml <$> genKeyAnyValueList ]
[ TOML <$> keyValues <*> tables <*> arrays ]
where
keyValues :: Gen (HashMap Key AnyValue)
keyValues = fromList <$> genKeyAnyValueList
tables :: Gen (PrefixMap TOML)
tables = fmap Toml.fromList
$ Gen.list (Range.linear 0 5)
$ (,) <$> genKey <*> genToml
arrays :: Gen (HashMap Key (NonEmpty TOML))
arrays = fmap fromList $ Gen.list (Range.linear 0 10) $ do
key <- genKey
arr <- Gen.list (Range.linear 1 10) genToml
pure (key, NE.fromList arr)
-- Date generators
genDay :: Gen Day
genDay = do
y <- toInteger <$> Gen.int (Range.constant 1968 2019)
m <- Gen.int (Range.constant 1 12)
d <- Gen.int (Range.constant 1 28)
pure $ fromGregorian y m d
genTimeOfDay :: Gen TimeOfDay
genTimeOfDay = do
secs <- MkFixed <$> Gen.integral (Range.constant 0 61)
mins <- Gen.int (Range.constant 0 59)
hours <- Gen.int (Range.constant 0 23)
pure $ TimeOfDay hours mins secs
genLocalTime :: Gen LocalTime
genLocalTime = do
day <- genDay
LocalTime day <$> genTimeOfDay
genZonedTime :: Gen ZonedTime
genZonedTime = do
local <- genLocalTime
zMin <- Gen.int (Range.constant (-720) 720)
let zTime = minutesToTimeZone zMin
pure $ ZonedTime local zTime
-- Primitive generators
range100 :: Range Int
range100 = Range.constant 0 100
genBool :: Gen Bool
genBool = Gen.bool
genInt :: Gen Int
genInt = Gen.int Range.constantBounded
genInteger :: Gen Integer
genInteger = toInteger <$> genInt
genDouble :: Gen Double
genDouble = Gen.frequency
[ (10, Gen.double $ Range.constant @Double (-1000000.0) 1000000.0)
, (1, Gen.constant $ 1/0)
, (1, Gen.constant $ -1/0)
, (1, Gen.constant $ 0/0)
]
genWord :: Gen Word
genWord = Gen.word Range.constantBounded
genWord8 :: Gen Word8
genWord8 = Gen.word8 Range.constantBounded
genNatural :: Gen Natural
genNatural = fromIntegral <$> genWord
genFloat :: Gen Float
genFloat = Gen.float (Range.constant (-10000.0) 10000.0)
genSet :: Ord a => Gen a -> Gen (Set a)
genSet genA = fromList <$> genList genA
genHashSet :: (Eq a, Hashable a) => Gen a -> Gen (HashSet a)
genHashSet genA = fromList <$> genList genA
genNonEmpty :: Gen a -> Gen (NonEmpty a)
genNonEmpty = Gen.nonEmpty (Range.constant 1 5)
genList :: Gen a -> Gen [a]
genList = Gen.list range100
genSmallList :: Gen a -> Gen [a]
genSmallList = Gen.list $ Range.constant 0 10
genIntSet :: Gen IntSet
genIntSet = fromList <$> genList genInt
genMap :: Ord k => Gen k -> Gen v -> Gen (Map k v)
genMap genK genV = Map.fromList <$> genSmallList (liftA2 (,) genK genV)
-- | Generatates control sympol.
genEscapeSequence :: Gen Text
genEscapeSequence = Gen.element
[ "\n", "\b", "\f", "\r", "\t", "\\", "\"" ]
-- | Generatates punctuation.
genPunctuation :: Gen Text
genPunctuation = Gen.element
[ ",", ".", ":", ";", "'", "?", "!", "`"
, "-", "_", "*", "$", "#", "@", "(", ")"
, " ", "^", "#", "/","&", ">", "<"
]
-- | Generatates n length list of hex chars.
genDiffHex :: Int -> Gen String
genDiffHex n = replicateM n Gen.hexit
-- | Generates unicode color string (u1234)
genUniHex4Color :: Gen Text
genUniHex4Color = do
hex <- genDiffHex 4
pure . Text.pack $ "\\u" ++ hex
-- | Generates unicode color string (u12345678)
genUniHex8Color :: Gen Text
genUniHex8Color = do
hex <- genDiffHex 8
pure . Text.pack $ "\\U" ++ hex
-- | Generates some unescaped unicode string
genUnicodeChar :: Gen Text
genUnicodeChar = Gen.element
[ "č", "ć", "š", "đ", "ž", "Ö", "ё"
, "в", "ь", "ж", "ю", "ч", "ü", "я"
]
-- | Generates text from different symbols.
genText :: Gen Text
genText = genNotEscape $ fmap Text.concat $ Gen.list (Range.constant 0 256) $ Gen.choice
[ Text.singleton <$> Gen.alphaNum
, genEscapeSequence
, genPunctuation
, genUniHex4Color
, genUniHex8Color
--, genUnicodeChar
]
genString :: Gen String
genString = Text.unpack <$> genText
genByteString :: Gen ByteString
genByteString = Gen.utf8 range100 Gen.alphaNum
genLByteString :: Gen LB.ByteString
genLByteString = LB.fromStrict <$> genByteString
genLText :: Gen L.Text
genLText = L.fromStrict <$> genText
-- | List of AnyValue generators.
noneArrayList :: [Gen AnyValue]
noneArrayList =
[ AnyValue . Bool <$> genBool
, AnyValue . Integer <$> genInteger
, AnyValue . Double <$> genDouble
, AnyValue . Text <$> genText
, AnyValue . Zoned <$> genZonedTime
, AnyValue . Local <$> genLocalTime
, AnyValue . Day <$> genDay
, AnyValue . Hours <$> genTimeOfDay
]
genArrayFrom :: Gen AnyValue -> Gen (Value 'TArray)
genArrayFrom noneArray = do
eVal <- toMArray <$> Gen.list (Range.constant 0 5) noneArray
case eVal of
Left err -> error $ show err
Right val -> pure val
{- | Generate arrays and nested arrays. For example:
Common array:
@
Array
[ Double (-5.7)
, Double (-6.4)
, Double 1.3
]
@
Nested array of AnyValue:
@
Array
[ Array
[ Text "AH",Text "HA"]
, Array [Integer 9,Integer (-3)]
, Array []
]
]
@
-}
genArray :: Gen (Value 'TArray)
genArray = Gen.recursive Gen.choice
[Gen.choice $ map genArrayFrom noneArrayList]
[Array <$> Gen.list (Range.constant 0 5) genArray]
-- filters
-- | Discards strings that end with \
genNotEscape :: Gen Text -> Gen Text
genNotEscape gen = gen >>= \t ->
if | Text.null t -> pure t
| Text.last t == '\\' -> Gen.discard
| otherwise -> pure t
-- Orphan instances
instance Eq ZonedTime where
(ZonedTime a b) == (ZonedTime c d) = a == c && b == d