tomland-0.5.0: test/Test/Toml/Gen.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- | This module contains all generators for @tomland@ testing.
module Test.Toml.Gen
( -- * Property
PropertyTest
, prop
-- * Generators
, genVal
, genKey
, genPrefixMap
, genToml
) where
import Control.Applicative (liftA2)
import Control.Monad (forM, replicateM)
import Data.Fixed (Fixed (..))
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Time (Day, LocalTime (..), TimeOfDay (..), ZonedTime (..), fromGregorian,
minutesToTimeZone)
import GHC.Stack (HasCallStack)
import Hedgehog (MonadGen, PropertyT, property)
import Test.Tasty (TestName, TestTree)
import Test.Tasty.Hedgehog (testProperty)
import Toml.BiMap (toMArray)
import Toml.PrefixTree (pattern (:||), Key (..), Piece (..), PrefixMap, PrefixTree (..), fromList)
import Toml.Type (AnyValue (..), DateTime (..), TOML (..), TValue (..), Value (..))
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
----------------------------------------------------------------------------
-- Property test creator
----------------------------------------------------------------------------
type PropertyTest = [TestTree]
prop :: HasCallStack => TestName -> PropertyT IO () -> [TestTree]
prop testName = pure . testProperty testName . property
----------------------------------------------------------------------------
-- Common generators
----------------------------------------------------------------------------
type V = Int
genVal :: MonadGen m => m V
genVal = Gen.int (Range.constant 0 256)
-- TODO: Arrays and Date.
-- | Generates random value of 'AnyValue' type.
genAnyValue :: MonadGen m => m AnyValue
genAnyValue = Gen.choice $
(AnyValue <$> genArray) : noneArrayList
-- TODO: unicode support
genPiece :: MonadGen m => m Piece
genPiece = Piece <$> Gen.text (Range.constant 1 50) Gen.alphaNum
genKey :: MonadGen m => m Key
genKey = Key <$> Gen.nonEmpty (Range.constant 1 10) genPiece
genKeyAnyValue :: MonadGen m => m (Key, AnyValue)
genKeyAnyValue = liftA2 (,) genKey genAnyValue
genKeyAnyValueList :: MonadGen m => m [(Key, AnyValue)]
genKeyAnyValueList = Gen.list (Range.linear 0 10) genKeyAnyValue
-- Generates key-value pair for PrefixMap
genEntry :: MonadGen m => m (Piece, Key)
genEntry = genKey >>= \case
key@(piece :|| _) -> pure (piece, key)
genPrefixMap :: MonadGen m => m (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 $ HashMap.fromList kvps
genPrefixTree :: forall m . MonadGen m => Key -> m (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 -> m (PrefixTree V)
genBranch prefMap = do
prefVal <- Gen.maybe genVal
pure $ Branch key prefVal prefMap
genTableHeader :: MonadGen m => m (Key, TOML)
genTableHeader = do
k <- genKey
toml <- makeToml <$> genKeyAnyValueList
pure (k, toml)
where
makeToml :: [(Key, AnyValue)] -> TOML
makeToml kv = TOML (HashMap.fromList kv) mempty
genToml :: MonadGen m => m TOML
genToml = do
kv <- HashMap.fromList <$> genKeyAnyValueList
tables <- Gen.list (Range.linear 0 10) genTableHeader
pure $ TOML kv (fromList tables)
genDay :: MonadGen m => m 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
genHours :: MonadGen m => m TimeOfDay
genHours = 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
genLocal :: MonadGen m => m LocalTime
genLocal = do
day <- genDay
LocalTime day <$> genHours
genZoned :: MonadGen m => m ZonedTime
genZoned = do
local <- genLocal
zMin <- Gen.int (Range.constant (-720) 720)
let zTime = minutesToTimeZone zMin
pure $ ZonedTime local zTime
genDate :: MonadGen m => m DateTime
genDate = Gen.choice
[ Day <$> genDay
, Hours <$> genHours
, Local <$> genLocal
, Zoned <$> genZoned
]
genBool :: MonadGen m => m Bool
genBool = Gen.bool
genInteger :: MonadGen m => m Integer
genInteger = toInteger <$> Gen.int (Range.constantBounded @Int)
genDouble :: MonadGen m => m Double
genDouble = Gen.frequency
[ (50, Gen.double $ Range.constant @Double (-1000000.0) 1000000.0)
, (5, Gen.constant $ 1/0)
, (5, Gen.constant $ -1/0)
, (5, Gen.constant $ 0/0)
]
-- | Generatates control sympol.
genEscapeSequence :: MonadGen m => m Text
genEscapeSequence = Gen.element
[ "\n", "\b", "\f", "\r", "\t", "\\", "\"" ]
-- | Generatates punctuation.
genPunctuation :: MonadGen m => m Text
genPunctuation = Gen.element
[ ",", ".", ":", ";", "'", "?", "!", "`"
, "-", "_", "*", "$", "#", "@", "(", ")"
, " ", "^", "#", "/","&", ">", "<"
]
-- | Generatates n length list of hex chars.
genDiffHex :: MonadGen m => Int -> m String
genDiffHex n = replicateM n Gen.hexit
-- | Generates unicode color string (u1234)
genUniHex4Color :: MonadGen m => m Text
genUniHex4Color = do
hex <- genDiffHex 4
pure . Text.pack $ "\\u" ++ hex
-- | Generates unicode color string (u12345678)
genUniHex8Color :: MonadGen m => m Text
genUniHex8Color = do
hex <- genDiffHex 8
pure . Text.pack $ "\\U" ++ hex
-- | Generates text from different symbols.
genText :: MonadGen m => m Text
genText = fmap Text.concat $ Gen.list (Range.constant 0 256) $ Gen.choice
[ Text.singleton <$> Gen.alphaNum
, genEscapeSequence
, genPunctuation
, genUniHex4Color
, genUniHex8Color
]
-- | List of AnyValue generators.
noneArrayList :: MonadGen m => [m AnyValue]
noneArrayList =
[ AnyValue . Bool <$> genBool
, AnyValue . Integer <$> genInteger
, AnyValue . Double <$> genDouble
, AnyValue . Text <$> genText
, AnyValue . Date <$> genDate
]
genArrayFrom :: MonadGen m => m AnyValue -> m (Value 'TArray)
genArrayFrom noneArray
= fromMaybe (error "Error in genArrayFrom")
. toMArray
<$> Gen.list (Range.constant 0 5) noneArray
{- | Generate arrays and nested arrays. For example:
Common array:
Array [Double (-563397.0197456297),Double (-308866.62837749254),Double (-29555.32072604308),Double 772371.8575471763,Double (-880016.1210667372),Double 182763.78796234122,Double (-462893.41157520143),Double 814856.6483699235,Double (-454629.17640282493)]
Nested array of AnyValue:
Array [Array [Text "ACyz38VcLz0hxwdFkHTU6PYK8h8CeaiEpI2xAaiZTKBQ3zC1W717cZY35lk8EAK6pPw3WvwIdNktxIV2LrvFSpU8ee6zkXvpvePitW9aspAeeOCF9Q9ry20y7skFZ2qShi7CSx8888zWIqyc8iBkoLNvq4fONLtuUqSw2SlNee4hDIwrnx5O4RuHW1dQfJcnC34h9S0DlIGYP08qq6QHxO4E0HE74cNmiViGm3xpDC8Ro5D8Y6p0FLSN1ELq9Lwm",Text "HhNv0LKICdlKxN"],Array [Integer 986479839551009895,Integer 8636972066308796678,Integer (-3464941350081979804),Integer (-6560688879547055621),Integer (-4749037439349044738)],Array []]
-}
genArray :: MonadGen m => m (Value 'TArray)
genArray = Gen.recursive Gen.choice
[Gen.choice $ map genArrayFrom noneArrayList]
[Array <$> Gen.list (Range.constant 0 5) genArray]