cartel-0.14.2.0: tests/Cartel/Generators.hs
{-# LANGUAGE TypeSynonymInstances #-}
module Cartel.Generators where
import Cartel
import Cartel.Betsy
import Cartel.Ast
import Cartel.Render
import Control.Applicative
import Control.Monad.Trans.State
import Control.Monad.Trans.Class
import System.Random (StdGen, random, randomR, Random(..), mkStdGen)
import Control.Monad.Trans.Reader
import Control.Monad
import Data.List (nub, sort)
type Gen = ReaderT Word (Betsy (State StdGen))
class Arbitrary a where
arbitrary :: Gen a
getSize :: Gen Word
getSize = ask
resize :: Word -> Gen a -> Gen a
resize w = withReaderT (const w)
getStdGen :: Gen StdGen
getStdGen = lift . lift $ get
setStdGen :: StdGen -> Gen ()
setStdGen = lift . lift . put
choose :: Random a => (a, a) -> Gen a
choose pair = do
g <- getStdGen
let (r, g') = randomR pair g
setStdGen g'
return r
chooseDefault :: Random a => Gen a
chooseDefault = do
g <- getStdGen
let (r, g') = random g
setStdGen g'
return r
frequency :: [(Int, Gen a)] -> Gen a
frequency [] = error "frequency used with empty list"
frequency xs0 = choose (1, tot) >>= (`pick` xs0)
where
tot = sum (map fst xs0)
pick n ((k, x):xs)
| n <= k = x
| otherwise = pick (n - k) xs
pick _ _ = error "pick used with empty list"
elements :: [a] -> Gen a
elements [] = error "elements used with empty list"
elements xs = (xs !!) `fmap` choose (0, length xs - 1)
oneOf :: [Gen a] -> Gen a
oneOf = join . elements
instance Arbitrary Bool where
arbitrary = chooseDefault
instance Arbitrary Word where
arbitrary = choose (0, 255)
instance Arbitrary a => Arbitrary (Maybe a) where
arbitrary = frequency [(3, fmap Just arbitrary), (1, return Nothing)]
instance Arbitrary a => Arbitrary [a] where
arbitrary = do
sz <- getSize
c <- choose (0, sz)
replicateM (fromIntegral c) arbitrary
instance Arbitrary Char where
arbitrary = choose ('a', 'z')
genNonEmptyString :: Gen NonEmptyString
genNonEmptyString = liftM2 (:) arbitrary (resize 5 arbitrary)
instance Arbitrary Vcs where
arbitrary = oneOf $ fmap cvs genNonEmptyString
: map return [ darcs, git, svn, mercurial,
bazaar, archVcs, monotone]
instance Arbitrary RepoKind where
arbitrary = elements [repoHead, repoThis]
instance Arbitrary Repository where
arbitrary = Repository <$> arbitrary <*> arbitrary <*> arbitrary
<*> genNonEmptyString <*> arbitrary <*> arbitrary
listOf :: Gen a -> Gen [a]
listOf g = do
sz <- getSize
len <- choose (0, sz)
replicateM (fromIntegral len) g
genNonEmptyList :: Gen a -> Gen [a]
genNonEmptyList g = liftM2 (:) g (listOf g)
genVersion :: Gen Version
genVersion = resize 3 (genNonEmptyList (choose (0, 99)))
instance Arbitrary Constraint where
arbitrary = do
sz <- getSize
lvls <- choose (0, sz)
go lvls
where
go lvl | lvl == 0 = oneOf
[ fmap lt genVersion
, fmap gt genVersion
, fmap eq genVersion
, fmap ltEq genVersion
, fmap gtEq genVersion
, return anyVersion
]
| otherwise = do
l <- next
r <- next
op <- elements [ (&&&), (|||) ]
return $ l `op` r
where
next = choose (0, lvl - 1) >>= go
instance Arbitrary Compiler where
arbitrary = elements [ ghc, nhc, yhc, hugs, helium, jhc, lhc ]
instance Arbitrary Condition where
arbitrary = do
sz <- getSize
lvls <- choose (0, sz)
goCondition lvls
goCondition :: Word -> Gen Condition
goCondition lvl
| lvl == 0 = lift currentFlags >>= lowest
| otherwise = upper
where
lowest [] = nonFlagLevel
lowest xs = elements (fmap (\(Flag nm _) -> nm) xs) >>= withFlagLevel
nonFlagLevel = oneOf [ fmap system genNonEmptyString
, fmap arch genNonEmptyString
, liftM2 impl arbitrary arbitrary
, return true
, return false
]
withFlagLevel fl = frequency
[ (2, nonFlagLevel), (1, return $ flag fl)]
upper = frequency [(2, binary), (1, inverted)]
where
next = choose (0, lvl - 1) >>= goCondition
inverted = fmap invert next
binary = do
op <- elements [(&&&), (|||)]
liftM2 op next next
genCondBlock :: (Arbitrary a, HasBuildInfo a) => Gen a
genCondBlock = liftM3 condBlock arbitrary
(liftM2 (,) arbitrary arbitrary) arbitrary
instance Arbitrary Package where
arbitrary = liftM2 package genNonEmptyString arbitrary
genBuildInfo :: HasBuildInfo a => Gen a
genBuildInfo = oneOf
[ return haskell98
, return haskell2010
, fmap buildDepends arbitrary
, fmap otherModules neslist
, fmap hsSourceDirs neslist
, fmap extensions neslist
, fmap defaultExtensions neslist
, fmap otherExtensions neslist
, fmap buildTools (genNonEmptyList arbitrary)
, fmap buildable arbitrary
, fmap ghcOptions neslist
, fmap ghcProfOptions neslist
, fmap ghcSharedOptions neslist
, fmap hugsOptions neslist
, fmap nhc98Options neslist
, fmap includes neslist
, fmap installIncludes neslist
, fmap includeDirs neslist
, fmap cSources neslist
, fmap extraLibraries neslist
, fmap ccOptions neslist
, fmap cppOptions neslist
, fmap ldOptions neslist
, fmap pkgConfigDepends (genNonEmptyList arbitrary)
, fmap frameworks neslist
]
where
neslist = genNonEmptyList genNonEmptyString
genMainIs :: BuildsExe a => Gen a
genMainIs = fmap mainIs genNonEmptyString
instance Arbitrary FlagOpts where
arbitrary = liftM3 FlagOpts arbitrary arbitrary arbitrary
addFlagsToState
:: Word
-- ^ How many flags?
-> Gen ()
addFlagsToState nFlags = do
allNames <- replicateM (fromIntegral nFlags) genNonEmptyString
let names = nub . sort $ allNames
mkf nm = do
opts <- arbitrary
_ <- lift (makeFlag nm opts)
return ()
_ <- mapM mkf names
return ()
downsize :: Gen a -> Gen a
downsize g = do
sz <- getSize
resize (sz - 1) g
instance Arbitrary LibraryField where
arbitrary = getSize >>= f
where
nonNestGens =
[ (6, genBuildInfo)
, (1, fmap exposed arbitrary)
, (1, fmap exposedModules (genNonEmptyList genNonEmptyString))
]
f sz | sz == 0 = nonNest
| otherwise = nest
where
nonNest = frequency nonNestGens
nest = downsize . frequency $
(1, genCondBlock) : nonNestGens
instance Arbitrary ExecutableField where
arbitrary = getSize >>= f
where
nonNestGens =
[ (6, genBuildInfo)
, (1, genMainIs)
]
nestGens = (1, genCondBlock) : nonNestGens
f sz | sz == 0 = frequency nonNestGens
| otherwise = downsize $ frequency nestGens
instance Arbitrary TestSuiteField where
arbitrary = getSize >>= f
where
nonNestGens =
[ (7, genBuildInfo)
, (1, fmap testModule genNonEmptyString)
, (1, pure exitcodeStdio)
, (1, pure detailed)
, (1, genMainIs)
]
nestGens = (1, genCondBlock) : nonNestGens
f sz | sz == 0 = frequency nonNestGens
| otherwise = downsize $ frequency nestGens
instance Arbitrary BenchmarkField where
arbitrary = getSize >>= f
where
nonNest =
[ (6, genBuildInfo)
, (1, return exitcodeStdio)
]
nest = (1, genCondBlock) : nonNest
f sz | sz == 0 = frequency nonNest
| otherwise = downsize $ frequency nest
instance Arbitrary BuildType where
arbitrary = elements [simple, configure, make, custom]
instance Arbitrary License where
arbitrary = elements [ gpl, agpl, lgpl, bsd2, bsd3, bsd4, mit, mpl,
apache, publicDomain, allRightsReserved,
otherLicense ]
instance (Arbitrary a, Arbitrary b) => Arbitrary (a, b) where
arbitrary = liftM2 (,) arbitrary arbitrary
instance Arbitrary Properties where
arbitrary = Properties
<$> genNonEmptyString -- name
<*> genVersion -- version
<*> arbitrary -- cabalVersion
<*> arbitrary -- buildType
<*> arbitrary -- license
<*> arbitrary -- licenseFile
<*> listOf genNonEmptyString -- licenseFiles
<*> arbitrary -- copyright
<*> arbitrary -- author
<*> arbitrary
<*> arbitrary
<*> arbitrary
<*> arbitrary -- bugReports
<*> arbitrary
<*> arbitrary
<*> arbitrary -- description
<*> arbitrary
<*> arbitrary
<*> listOf genNonEmptyString -- dataFiles
<*> arbitrary
<*> listOf genNonEmptyString
<*> listOf genNonEmptyString
<*> listOf genNonEmptyString -- extraTmpFiles
instance Arbitrary Section where
arbitrary = frequency [(3, nonRepo), (1, repo)]
where
nonRepo = oneOf
[ liftM2 executable genNonEmptyString arbitrary
, liftM2 testSuite genNonEmptyString arbitrary
, liftM2 benchmark genNonEmptyString arbitrary
]
repo = fmap repository arbitrary
instance Arbitrary Cabal where
arbitrary = do
sz <- getSize
nFlags <- choose (0, sz)
addFlagsToState nFlags
Cabal <$> arbitrary <*> arbitrary <*> arbitrary <*>
lift currentFlags
-- | Generates Cabal text given a size parameter and int to generate a
-- seed. Read the function from the bottom up:
--
-- * generates a random computation in ReaderT
--
-- * runs the ReaderT with the given size, resulting in a 'Betsy'
--
-- * runs the 'Betsy', which results in a State computation with the
-- 'StdGen' as the state
--
-- * runs the state computation with an initial state initialized by
-- the given seed
--
-- * fetches just the result of the state computation, discarding the
-- ending state
--
-- * fetches just the resulting Cabal record, discarding the flags
-- created in the 'Betsy'
--
-- * Renders either the error or the Cabal record
genCabalText
:: Word
-- ^ Size
-> Int
-- ^ Random seed
-> Either String String
-- ^ Either an error message, or the Cabal text.
genCabalText sz sd
= either (Left . renderNoIndent) (Right . renderNoIndent)
. fmap fst
. fst
. flip runState (mkStdGen sd)
. runBetsy
. flip runReaderT sz
$ (arbitrary :: Gen Cabal)
genResult
:: Gen a
-- ^ Generator
-> Word
-- ^ How many flags?
-> Word
-- ^ Size
-> Int
-- ^ Random seed
-> Either String a
genResult gen nFlags sz sd
= either (Left . renderNoIndent) (Right . fst)
. fst
. flip runState (mkStdGen sd)
. runBetsy
. flip runReaderT sz
$ (addFlagsToState nFlags >> gen)