reflex-vty-1.3.0.0: test/Reflex/Vty/Test/Generators.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Description: Bounded QuickCheck generators and the fixed-seed property runner
--
-- Every property in the suite runs through 'runPlanProperty'. It runs
-- 300 cases with maximum size 50. It uses a fixed replay seed, so
-- failures are reproducible. Generated domains are bounded and valid.
-- Specs contain explicit boundary cases, not these generators.
module Reflex.Vty.Test.Generators
( runPlanProperty
, planSeed
, genDim
, genPositiveDim
, genAsciiChar
, genAsciiText
, genMixedWidthText
, genLineText
, genFiniteDouble
, genEditScript
, EditOp (..)
) where
import Control.Monad (forM)
import Data.Char (chr)
import Data.Text (Text)
import qualified Data.Text as T
import Test.Hspec (Expectation, expectationFailure)
import Test.QuickCheck
import Test.QuickCheck.Random (mkQCGen)
-- | The fixed replay seed for the suite's properties.
planSeed :: Int
planSeed = 731947
-- | Run a property with the suite configuration: 300 cases, maximum
-- size 50, replay seed 'planSeed'. Failures report the seed and the
-- shrunk counterexample.
runPlanProperty :: Testable prop => prop -> Expectation
runPlanProperty prop = do
r <-
quickCheckWithResult
stdArgs
{ maxSuccess = 300
, maxSize = 50
, replay = Just (mkQCGen planSeed, 0)
, chatty = False
}
prop
case r of
Success {} -> pure ()
_ -> expectationFailure ("[fixed replay seed " ++ show planSeed ++ "]\n" ++ output r)
-- | A nonnegative dimension up to 40.
genDim :: Gen Int
genDim = choose (0, 40)
-- | A positive dimension up to 40.
genPositiveDim :: Gen Int
genPositiveDim = choose (1, 40)
-- | A printable ASCII character (no control characters, no tabs).
genAsciiChar :: Gen Char
genAsciiChar = chr <$> choose (32, 126)
-- | ASCII text of bounded length (at most 200).
genAsciiText :: Gen Text
genAsciiText = T.pack <$> scale (min 200) (listOf genAsciiChar)
-- | One line of mixed-width content: ASCII, wide CJK, zero-width
-- combining marks, and spaces. There are no newlines. Line-oriented
-- APIs receive explicit newlines from 'genEditScript' inserts instead.
genLineText :: Gen Text
genLineText =
fmap T.concat $
scale (min 100) $
listOf $
frequency
[ (6, T.singleton <$> genAsciiChar)
, (2, pure "\30028")
, (2, pure "\28450")
, (1, pure "\769")
, (1, pure " ")
]
-- | Multi-line mixed-width text (bounded).
genMixedWidthText :: Gen Text
genMixedWidthText = do
ls <- scale (min 20) (listOf genLineText)
pure (T.intercalate "\n" ls)
-- | A finite (non-NaN, non-infinite) double in [0, 2].
genFiniteDouble :: Gen Double
genFiniteDouble =
suchThat
(choose (0, 2))
(\d -> not (isNaN d) && not (isInfinite d))
-- | Editing operations for zipper scripts.
data EditOp
= OpLeft
| OpRight
| OpUp
| OpDown
| OpHome
| OpEnd
| OpPageUp !Int
| OpPageDown !Int
| OpInsertChar !Char
| OpInsertNewline
| OpInsertText !Text
| OpDeleteLeft
| OpDeleteRight
| OpDeleteLeftWord
| OpTab !Int
| OpTop
deriving (Eq, Show)
genEditOp :: Gen EditOp
genEditOp =
frequency
[ (3, pure OpLeft)
, (3, pure OpRight)
, (2, pure OpUp)
, (2, pure OpDown)
, (2, pure OpHome)
, (2, pure OpEnd)
, (1, OpPageUp <$> choose (-1, 3))
, (1, OpPageDown <$> choose (-1, 3))
, (6, OpInsertChar <$> genAsciiChar)
, (2, pure OpInsertNewline)
, (2, OpInsertText <$> scale (min 20) genLineText)
, (3, pure OpDeleteLeft)
, (3, pure OpDeleteRight)
, (2, pure OpDeleteLeftWord)
, (1, OpTab <$> choose (0, 8))
, (1, pure OpTop)
]
-- | An edit script of at most 100 operations.
genEditScript :: Gen [EditOp]
genEditScript = scale (min 100) (listOf genEditOp)