testing-feat (empty) → 0.1
raw patch · 12 files changed
+1304/−0 lines, 12 filesdep +QuickCheckdep +basedep +containerssetup-changed
Dependencies added: QuickCheck, base, containers, data-memocombinators, mtl, template-haskell
Files
- Control/Monad/TagShare.hs +62/−0
- LICENSE +30/−0
- Setup.lhs +3/−0
- Test/Feat.hs +24/−0
- Test/Feat/Access.hs +156/−0
- Test/Feat/Class.hs +246/−0
- Test/Feat/Enumerate.hs +162/−0
- Test/Feat/Internals/Derive.hs +48/−0
- Test/Feat/Internals/Tag.hs +5/−0
- Test/Feat/Modifiers.hs +63/−0
- examples/TestTH.hs +451/−0
- testing-feat.cabal +54/−0
@@ -0,0 +1,62 @@++-- | A monad for binding values to tags to ensure sharing, +-- with the added twist that the value can be polymorphic+-- and each monomorphic instance is bound separately.+module Control.Monad.TagShare(+ -- ** Dynamic map+ DynMap,+ dynEmpty,+ dynInsert,+ dynLookup,+ -- ** Sharing monad+ Sharing,+ runSharing,+ share+ ) where+import Control.Monad.State+import Data.Typeable+import Data.Dynamic(Dynamic, fromDynamic, toDyn)+import Data.Map as M++-- | A dynamic map with type safe+-- insertion and lookup.+newtype DynMap tag = + DynMap (M.Map (tag, TypeRep) Dynamic) + deriving Show++dynEmpty :: DynMap tag+dynEmpty = DynMap M.empty + +dynInsert :: (Typeable a, Ord tag) => + tag -> a -> DynMap tag -> DynMap tag+dynInsert u a (DynMap m) = + DynMap (M.insert (u,typeOf a) (toDyn a) m)++dynLookup :: (Typeable a, Ord tag) => + tag -> DynMap tag -> Maybe a+dynLookup u (DynMap m) = hlp fun undefined where + hlp :: Typeable a => + (TypeRep -> Maybe a) -> a -> Maybe a+ hlp f a = f (typeOf a)+ fun tr = M.lookup (u,tr) m >>= fromDynamic++ +-- | A sharing monad+-- with a function that binds a tag to a value.+type Sharing tag a = State (DynMap tag) a++runSharing :: Sharing tag a -> a+runSharing m = evalState m dynEmpty++share :: (Typeable a, Ord tag) => + tag -> Sharing tag a -> Sharing tag a+share t m = do+ mx <- gets $ (dynLookup t)+ case mx of+ Just e -> return e+ Nothing -> mfix $ \e -> do+ modify (dynInsert t e)+ m+++
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Jonas Duregrd++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Jonas Duregrd nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.lhs view
@@ -0,0 +1,3 @@+> import Distribution.Simple+> main :: IO ()+> main = defaultMain
+ Test/Feat.hs view
@@ -0,0 +1,24 @@+module Test.Feat(+ Enumerate(..),+ -- ** The type class+ Enumerable(..),+ nullary,+ unary,+ funcurry,+ consts,+ deriveEnumerable,+ FreePair(..),+ -- ** Accessing data+ optimised,+ index,+ values,+ bounded,+ uniform,+ ioAll,+ ioBounded + ) where++import Test.Feat.Access+import Test.Feat.Class+import Test.Feat.Enumerate+-- import Test.Feat.Modifiers
+ Test/Feat/Access.hs view
@@ -0,0 +1,156 @@+-- | Functions for accessing the values of enumerations including +-- compatability with the property based testing frameworks QuickCheck and+-- SmallCheck.+module Test.Feat.Access(+ -- ** Accessing functions+ index,+ values,+ striped,+ bounded,+ + -- ** A simple property tester+ ioFeat,+ ioAll,+ ioBounded,+ + -- ** Compatability+ -- *** QuickCheck+ uniform,+ -- *** SmallCheck+ toSeries,+ + -- ** Non-class versions of the access functions+ valuesWith,+ stripedWith,+ boundedWith,+ uniformWith,+ toSeriesWith+ )where++-- testing-feat+import Test.Feat.Enumerate +import Test.Feat.Class+-- base+import Data.List+-- quickcheck+import Test.QuickCheck+-- smallcheck+-- import Test.SmallCheck.Series -- Not needed++group :: Enumerate a -> Part -> Index -> Integer+group e p i = sum (map (card e) [0..p-1]) + i++split :: Enumerate a -> Integer -> (Part, Index)+split e i0 = go i0 0 where+ go i p = let crd = card e p in + if i < crd then (p,i)+ else go (i-crd) (p+1)++-- | Mainly as a proof of concept we can use the isomorphism between +-- natural numbers and (Part,Index) pairs to index into a type+-- May not terminate for finite types.+-- Might be slow the first time it is used with a specific enumeration +-- because cardinalities need to be calculated.+-- The computation complexity after cardinalities are computed is a polynomial+-- of the size of the resulting value.+index :: Enumerate a -> Integer -> a +index e = uncurry (select e) . split e++-- | All values of the enumeration by increasing cost (which is the number+-- of constructors for most types). Also contains the cardinality of each list.+values :: Enumerable a => [(Integer,[a])]+values = valuesWith optimised++-- | A generalisation of @values@ that enumerates every nth value of the +-- enumeration from a given starting point.+-- As a special case @values = striped 0 0 1@.+striped :: Enumerable a => Part -> Index -> Integer -> [(Integer,[a])]+striped = stripedWith optimised ++-- | A version of vales that has a limited number of values in each inner list.+-- If the list corresponds to a Part which is larger than the bound it evenly+-- intersperses the values across the enumeration of the Part.+bounded :: Enumerable a => Integer -> [(Integer,[a])]+bounded = boundedWith optimised++-- | A rather simple but general property testing driver.+-- The property is a (funcurried) IO function that both tests and reports the +-- error. The driver goes on forever or until the list is exhausted, +-- reporting the coverage and the number of+-- tests before each new part.+ioFeat :: [(Integer,[a])] -> (a -> IO ()) -> IO ()+ioFeat vs f = go vs 0 where+ go ((c,xs):xss) s = do+ putStrLn $ "--- Testing "++show c++" vales at size " ++ show s+ mapM f xs+ go xss (s+1)+ go [] s = putStrLn $ "--- Done. Tested "++ show s++" values"++-- | ioAll = 'ioFeat' values+ioAll :: Enumerable a => (a -> IO ()) -> IO ()+ioAll = ioFeat values++-- | ioBounded @n = 'ioFeat' (bounded n)@+ioBounded :: Enumerable a => Integer -> (a -> IO ()) -> IO ()+ioBounded n = ioFeat (bounded n)++++-- | Compatability with QuickCheck. Distribution is uniform generator over +-- values bounded by the given size. Typical use: @sized uniform@.+uniform :: Enumerable a => Int -> Gen a+uniform = uniformWith optimised++-- | Compatability with SmallCheck. +toSeries :: Enumerable a => Int -> [a] +toSeries = toSeriesWith optimised++-- | Non class version of 'values'.+valuesWith :: Enumerate a -> [(Integer,[a])]+valuesWith e = + [(crd,[select e p i|i <- [0..crd - 1]]) + |p <- [0..], let crd = card e p]++-- | Non class version of 'striped'.+stripedWith :: Enumerate a -> Part -> Index -> Integer -> [(Integer,[a])]+stripedWith e p o step = if space <= 0 + then (0,[]) : stripedWith e (p+1) (o - crd) step+ else (d,thisP) : stripedWith e (p+1) (step-m-1) step+ where+ thisP = + [select e p x|x <- genericTake d $ iterate (+step) o]+ space = crd - o+ (d,m) = divMod space step+ crd = card e p++-- | Non class version of 'bounded'.+boundedWith :: Enumerate a -> Integer -> [(Integer,[a])]+boundedWith e n = map (samplePart e n) [0..]++samplePart :: Enumerate a -> Index -> Part -> (Integer,[a])+samplePart e m p = + let+ top = toRational $ (card e p) - 1+ step = top / toRational (m-1) + crd = card e p+ in if toRational m >= top + then (crd, map (select e p) [0..crd - 1])+ else let d = floor ((toRational crd)/ step) in + (d+1,[select e p (round (k * step))|k <- map toRational [0..d]])++-- | Non class version of 'uniform'.+uniformWith :: Enumerate a -> Part -> Gen a+uniformWith e maxp = let+ cards = [(card e x, x) | x <- [maxp, maxp-1 .. 0]]+ tot = sum $ fst $ unzip cards+ in if tot == 0 then uniformWith e (maxp+1) else do+ i <- choose (0,tot-1)+ return $ uncurry (select e) (lu i cards)+ where+ lu i ((crd,p):xs) = if i<crd + then (p,i) + else lu (i-crd) xs+ +-- | Non class version of 'toSeries'.+toSeriesWith :: Enumerate a -> Int -> [a]+toSeriesWith e d = concat (take (d+1) $ map snd $ valuesWith e)
+ Test/Feat/Class.hs view
@@ -0,0 +1,246 @@+{-#LANGUAGE DeriveDataTypeable, TemplateHaskell #-}++-- | Everything you need to construct an enumeration for an algebraic type.+-- Just define each constructor using pure for nullary constructors and +-- unary and funcurry for positive arity constructors, then combine the +-- constructors with consts. Example:+-- +-- @+-- instance Enumerable a => Enumerable [a] where+-- enumerate = consts [unary (funcurry (:)), pure []]+-- @+--+-- There's also a handy Template Haskell function for automatic derivation.+++module Test.Feat.Class (+ Enumerable(..),+ + -- ** Building instances+ Constructor,+ nullary,+ unary,+ funcurry,+ consts,+ + -- ** Accessing the enumerator of an instance+ optimised,+ + -- *** Free pairs+ FreePair(..),+ + + -- ** Deriving instances with template haskell+ deriveEnumerable,+ -- autoCon,+ -- autoCons+ + ++ ) where++-- testing-feat+import Test.Feat.Enumerate+import Test.Feat.Internals.Tag(Tag(Class))+import Test.Feat.Internals.Derive+-- base+import Data.Typeable+-- template-haskell+import Language.Haskell.TH+import Language.Haskell.TH.Syntax+-- base - only for instances+import Data.Word+import Data.Int+import Data.Bits++-- | A class of functionally enumerable types+class Typeable a => Enumerable a where+ -- | This is the interface for defining an instance. Memoisation needs to + -- be ensured e.g. using 'mempay' but sharing is handled automatically by + -- the default implementation of 'shared'.+ enumerate :: Enumerate a+ + -- | Version of enumerate that ensures it is shared between+ -- all accessing functions. Should alwasy be used when + -- combining enumerations.+ -- Should typically be left to default behaviour.+ shared :: Enumerate a+ shared = tagShare Class enumerate++-- | An optimised version of enumerate. Used by all+-- library functions that access enumerated values (but not +-- by combining functions). Library functions should ensure that +-- @optimised@ is not reevaluated.+optimised :: Enumerable a => Enumerate a+optimised = optimise shared ++-- | A free pair constructor. The cost of constructing a free pair+-- is equal to the sum of the costs of its components. +newtype FreePair a b = Free {free :: (a,b)} + deriving (Show, Typeable)++-- | Uncurry a function (typically a constructor) to a function on free pairs.+funcurry :: (a -> b -> c) -> FreePair a b -> c+funcurry f = uncurry f . free++instance (Enumerable a, Enumerable b) => + Enumerable (FreePair a b) where+ enumerate = mem $ curry Free <$> shared <*> shared++type Constructor = Enumerate+ +-- | For nullary constructors such as @True@ and @[]@.+nullary :: a -> Constructor a+nullary = pure++-- | For any non-nullary constructor. Apply 'funcurry' until the type of+-- the result is unary (i.e. n-1 times where n is the number of fields +-- of the constructor).+unary :: Enumerable a => (a -> b) -> Constructor b+unary f = f <$> shared++-- | Produces the enumeration of a type given the enumerators for each of its+-- constructors. The result of 'unary' should typically not be used +-- directly in an instance even if it only has one constructor. So you +-- should apply consts even in that case. +consts :: [Constructor a] -> Enumerate a+consts xs = mempay $ mconcat xs +++--------------------------------------------------------------------+-- Automatic derivation++-- | Derive an instance of Enumberable with Template Haskell.+deriveEnumerable :: Name -> Q [Dec]+deriveEnumerable = fmap return . instanceFor ''Enumerable [enumDef]++-- -- | Derive the enumeration of a single constructor. Useful +-- if 'deriveEnumerable' does not work for all constructors. +-- autoCon :: Name -> Q Exp+-- autoCon = undefined++-- -- | Splices a list of automatically derived constructors.+-- autoCons :: [Name] -> Q Exp+-- autoCons = listE . map autoCon++enumDef :: [(Name,[Type])] -> [Q Dec]+enumDef cons = [fmap mk_freqs_binding [|consts $ex |]] where+ ex = listE $ map cone cons+ cone (n,[]) = [|pure $(conE n)|]+ cone (n,_:vs) = + [|unary $(foldr appE (conE n) (map (const [|funcurry|] ) vs) )|]+ mk_freqs_binding :: Exp -> Dec+ mk_freqs_binding e = ValD (VarP 'enumerate) (NormalB e) []++++++---------------------------------------------------------------------+-- Instances+++(let + it = mapM (instanceFor ''Enumerable [enumDef]) + [ ''[] + , ''Bool+ , ''()+ , ''(,)+ , ''(,,)+ , ''(,,,)+ , ''(,,,,)+ , ''(,,,,,)+ , ''(,,,,,,) -- This is as far as typeable goes...+ , ''Either+ , ''Maybe+ , ''Ordering+ ]+ -- Circumventing the stage restrictions by means of code repetition.+ enumDef :: [(Name,[Type])] -> [Q Dec]+ enumDef cons = [fmap mk_freqs_binding [|consts $ex |]] where+ ex = listE $ map cone cons+ cone (n,[]) = [|pure $(conE n)|]+ cone (n,_:vs) = + [|unary $(foldr appE (conE n) (map (const [|funcurry|] ) vs) )|]+ mk_freqs_binding :: Exp -> Dec+ mk_freqs_binding e = ValD (VarP 'enumerate) (NormalB e) []+ in it)+ +++-- This instance is quite important. It needs to be exponential for +-- the other instances to work.+newtype Natural = Natural {natural :: Integer} deriving (Typeable, Show)+instance Enumerable Natural where + enumerate = let e = Enumerate{+ card = crd,+ select = sel,+ optimal = return e} in e where+ crd p+ | p <= 0 = 0+ | p == 1 = 1+ | otherwise = 2^(p-2)+ sel 1 0 = Natural 0+ sel p i = Natural $ 2^(p-2) + i++-- This instance is used by the Int* instances and needs to be exponential as +-- well.+instance Enumerable Integer where + enumerate = unary f where+ f (Free (b,Natural i)) = if b then -i-1 else i+ ++-- An exported version would have to use $tag instead of Class+word :: (Bits a, Integral a) => Enumerate a +word = e where+ e = cutOff (bitSize' e+1) $ unary (fromInteger . natural)+ +int :: (Bits a, Integral a) => Enumerate a +int = e where+ e = cutOff (bitSize' e+1) $ unary fromInteger++cutOff :: Int -> Enumerate a -> Enumerate a +cutOff n e = e{+ card = \p -> if p > n then 0 else card e p, + optimal = fmap (cutOff n) $ optimal e+ }++bitSize' :: Bits a => f a -> Int+bitSize' f = hlp undefined f where+ hlp :: Bits a => a -> f a -> Int+ hlp a _ = bitSize a++instance Enumerable Word where+ enumerate = word+instance Enumerable Word8 where+ enumerate = word+instance Enumerable Word16 where+ enumerate = word+instance Enumerable Word32 where+ enumerate = word+instance Enumerable Word64 where+ enumerate = word++instance Enumerable Int where+ enumerate = int+instance Enumerable Int8 where+ enumerate = int+instance Enumerable Int16 where+ enumerate = int+instance Enumerable Int32 where+ enumerate = int+instance Enumerable Int64 where+ enumerate = int++-- | Not injective+instance Enumerable Double where+ enumerate = unary (funcurry encodeFloat)++-- | Not injective+instance Enumerable Float where+ enumerate = unary (funcurry encodeFloat)++-- | Contains only ASCII characters+instance Enumerable Char where+ enumerate = cutOff 8 $ unary (toEnum . fromIntegral :: Word -> Char)+
+ Test/Feat/Enumerate.hs view
@@ -0,0 +1,162 @@+{-#LANGUAGE DeriveDataTypeable, TemplateHaskell #-}++-- | Basic combinators fo building enumerations+-- most users will want to use the type class +-- based combinators in "Test.Feat.Class" instead. ++module Test.Feat.Enumerate(+ + Part,+ Index,+ Enumerate(..),+ + -- ** Combinators for building enumerations+ module Control.Applicative,+ module Data.Monoid,+ pay,+ + -- ** Memoisation+ mem,+ mempay,+ + -- *** Polymorphic memoisation+ module Data.Typeable,+ Tag(Source),+ tag,+ tagShare,+ optimise + ) where++-- testing-feat+import Control.Monad.TagShare(Sharing, runSharing, share)+import Test.Feat.Internals.Tag(Tag(Source))+-- base+import Control.Applicative+import Control.Monad+import Data.Monoid+import Data.Typeable+import Language.Haskell.TH+-- data-memocombinators+import Data.MemoCombinators++++type Part = Int+type Index = Integer++-- | A functional enumeration of type t is a partition of+-- t into finite numbered sets called Parts. The number that+-- identifies each part is called the cost of the values in +-- that part.+data Enumerate a = Enumerate+ { + -- | Computes the cardinality of a given part.+ card :: Part -> Index,+ -- | Selects a value from the enumeration+ -- For @select e p i@, @i@ should be less than @card e p@+ select :: Part -> Index -> a,+ -- | A self-optimising function. + optimal :: Sharing Tag (Enumerate a)+ } deriving Typeable ++-- | Only use fmap with bijective functions (e.g. data constructors)+instance Functor Enumerate where + fmap f cf = cf+ {select = \p n -> f (select cf p n)+ , optimal = liftM (fmap f) (optimal cf) }++-- | mappend = union+instance Monoid (Enumerate a) where+ mempty = let e = Enumerate (\p -> 0) + (\p i -> error "select: empty")+ (return e) in e+ mappend = union++-- | Disjoint union+union :: Enumerate a -> Enumerate a -> Enumerate a+union a b = infinite part (liftM2 union (optimal a) (optimal b)) where+ part p = finUnion (finite a p) (finite b p)++-- | <*> corresponds to product (as with lists)+instance Applicative Enumerate where+ pure = singleton+ f <*> a = fmap (uncurry ($)) (cartesian f a)++-- | The product of two enumerations+cartesian :: Enumerate a -> Enumerate b -> Enumerate (a,b)+cartesian a b = infinite (\p -> foldl1 finUnion+ [finCart (finite a x) (finite b (p-x))| x <- [0..p]])+ (liftM2 cartesian (optimal a) (optimal b))++-- | The definition of @pure@ for the applicaive instance. +singleton :: a -> Enumerate a+singleton a = let e = Enumerate car sel (return e) in e + where car p = if p == 0 then 1 else 0+ sel 0 0 = a+ sel _ _ = + error "select: index out of bounds"++-- | Increases the cost of all values in an enumeration by one.+pay :: Enumerate a -> Enumerate a+pay sel = Enumerate+ { card = \p -> if p <= 0 then 0 else card sel (p-1)+ , select = \p -> select sel (p-1)+ , optimal = liftM pay (optimal sel)+ }++-------------------------------------------------------+-- Memoisation++mem :: Enumerate a -> Enumerate a+mem sel = sel+ { card = bits (card sel)+ , optimal = fmap mem (optimal sel)+ }++-- | A conventient combination of memoisation and guarded recursion.+mempay :: Enumerate a -> Enumerate a+mempay = mem . pay+ ++-------------------------------------------------------+-- Polymorphic memoisation+tag :: Q Exp -- :: Tag+tag = location >>= makeTag where+ makeTag Loc{ loc_package = p, + loc_module = m, + loc_start = (r,c) }+ = [|Source p m r c|]++tagShare :: Typeable a => Tag -> Enumerate a -> Enumerate a+tagShare t e = e{optimal = share t (optimal e)}++optimise :: Enumerate a -> Enumerate a+optimise e = let e' = runSharing (optimal e) in+ e'{optimal = return e'} ++ +--------------------------------------------------------+-- Operations on finite sets+data Finite a = Finite {fCard :: Index, fSelect :: Index -> a}++finite :: Enumerate a -> Part -> Finite a+finite e p = Finite (card e p) (select e p) ++infinite :: (Part -> Finite a) -> Sharing Tag (Enumerate a) -> Enumerate a+infinite f m = Enumerate (fCard . f) (fSelect . f) m++finUnion :: Finite a -> Finite a -> Finite a+finUnion f1 f2 = Finite car sel where+ car = fCard f1 + fCard f2+ sel i = if i < fCard f1+ then fSelect f1 i+ else fSelect f2 (i-fCard f1) ++finCart :: Finite a -> Finite b -> Finite (a,b)+finCart f1 f2 = Finite car sel where+ car = fCard f1 * fCard f2+ sel i = let (q, r) = (i `quotRem` fCard f2) + in (fSelect f1 q, fSelect f2 r)+++
+ Test/Feat/Internals/Derive.hs view
@@ -0,0 +1,48 @@+{-#Language TemplateHaskell#-}+module Test.Feat.Internals.Derive where+import Language.Haskell.TH++-- General combinator for class derivation+instanceFor :: Name -> [[(Name,[Type])] -> [Q Dec]] -> Name -> Q Dec+instanceFor clname confs dtname = do+ (cxt,dtvs,cons) <- extractData dtname+ cd <- mapM conData cons+ let + mkCxt = fmap (cxt++) $ mapM (classP clname . return . varT) dtvs+ mkTyp = mkInstanceType clname dtname dtvs+ mkDecs conf = conf cd++ instanceD mkCxt mkTyp (concatMap mkDecs confs)+++mkInstanceType :: Name -> Name -> [Name] -> Q Type+mkInstanceType cn dn vns = appT (conT cn) (foldl (appT) (conT dn) (map varT vns))++extractData :: Name -> Q (Cxt, [Name], [Con])+extractData n = reify n >>= \i -> return $ case i of+ TyConI (DataD cxt _ tvbs cons _) -> (cxt, map tvbName tvbs, cons)+ TyConI (NewtypeD cxt _ tvbs con _) -> (cxt, map tvbName tvbs, [con])+ _ -> error $ "Unexpected info: " ++ show (ppr i)++tvbName :: TyVarBndr -> Name+tvbName (PlainTV n) = n+tvbName (KindedTV n _) = n+++conData :: Con -> Q (Name,[Type])+conData c = case c of+ NormalC n sts -> return (n,map snd sts)+ RecC n vsts -> return (n,map (\(_,s,t) -> t) vsts)+ InfixC st1 n st2 -> return (n,[snd st1,snd st2])+ ForallC _ _ c' -> conData c'+++x :: IO Type+x = runQ $ (toType ''(,)) + ++toType n = case lookup n tups of+ Nothing -> conT n+ Just q -> q++tups = (''(), [t|()|]):map (\(n,i) -> (n, tupleT i)) (zip [''(,), ''(,,)] [2..])
+ Test/Feat/Internals/Tag.hs view
@@ -0,0 +1,5 @@+module Test.Feat.Internals.Tag where++data Tag = Class + | Source String String Int Int+ deriving (Show,Eq,Ord)
+ Test/Feat/Modifiers.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE DeriveDataTypeable #-}++-- | Types with invariants. Currently these are mostly examples of how to +-- define such types, suggestions on useful types are appreciated.+--+-- To use the invariant types you can use the record label. For instance:+--+-- @+-- data C a = C [a] [a] deriving Typeable+-- instance Enumerable a => Enumerable (C a) where+-- enumerate = unary $ funcurry $ +-- \xs ys -> C (nonEmpty xs) (nonEmpty ys)+-- @+--+-- Alternatively you can put everything in pattern postition:+--+-- @+-- instance Enumerable a => Enumerable (C a) where+-- enumerate = unary $ funcurry $ +-- \(Free (NonEmpty xs,NonEmpty ys)) -> C xs ys)+-- @+--+-- The first approach has the advantage of being usable with a +-- point free style: @ \xs -> C (nonEmpty xs) . nonEmpty @.+module Test.Feat.Modifiers(+ NonEmpty(..),+ mkNonEmpty,++ Nat(..),+ + ) where++-- testing-feat+import Test.Feat.Enumerate +import Test.Feat.Class+-- quickcheck -- Should be made compatible at some point.+-- import Test.QuickCheck.Modifiers+++-- | A type of non empty lists.+newtype NonEmpty a = NonEmpty {nonEmpty :: [a]} + deriving (Typeable, Show)+mkNonEmpty x xs = x:xs+instance Enumerable a => Enumerable (NonEmpty a) where+ enumerate = unary NonEmpty++-- Copy paste from Enumerate.hs+-- | A type of natural numbers.+newtype Nat = Nat {nat :: Integer} + deriving (Typeable, Show)+instance Enumerable Nat where + enumerate = let e = Enumerate{+ card = crd,+ select = sel,+ optimal = return e} in e where+ crd p+ | p <= 0 = 0+ | p == 1 = 1+ | otherwise = 2^(p-2)+ sel 1 0 = Nat 0+ sel p i = Nat $ 2^(p-2) + i++
+ examples/TestTH.hs view
@@ -0,0 +1,451 @@+{-#LANGUAGE MagicHash, TemplateHaskell, DeriveDataTypeable, StandaloneDeriving, GeneralizedNewtypeDeriving #-} +-- BangPatterns, ScopedTypeVariables, ViewPatterns, KindSignatures++module TestTH where ++import Language.Haskell.TH.Syntax+ ( Exp(..), Pat(..), Stmt(..), Type(..), Dec(..), + Range(..), Lit(..), Kind(..), + Body(..), Guard(..), Con(..), Match(..), + Name(..), mkName, NameFlavour(..), NameSpace(..), + Clause(..), Pragma(..), FamFlavour(..), + Pred(..), TyVarBndr(..), + Foreign, Callconv(..), FunDep(..), + Safety(..), Strict(..), InlineSpec(..))+-- testing-feat+import Test.Feat+import Test.Feat.Access+import Test.Feat.Modifiers+-- template-haskell+import Language.Haskell.TH.Syntax.Internals(OccName(OccName), ModName(ModName), PkgName)+import Language.Haskell.TH.Ppr(pprint,Ppr)+-- haskell-src-meta+import Language.Haskell.Meta(toExp)+-- haskell-src-exts+import qualified Language.Haskell.Exts as E+-- quickcheck+import Test.QuickCheck hiding (NonEmpty, (><))+--base+import Data.Typeable(Typeable)+import Data.Ord+import Data.List+-- smallcheck+import Test.SmallCheck.Series hiding (Nat)+import Test.SmallCheck++-- Currently both of these spit out a lot of errors. Disabling a few of the+-- buggier constructors might help.+test_parsesAll = ioAll report_parses+test_parsesBounded = ioBounded 10000 report_parses++report_parses e = case prop_parsesM e of+ Nothing -> return ()+ Just s -> do+ putStrLn "Failure:"+ putStrLn (pprint e)+ print e+ putStrLn s+ putStrLn ""++prop_parsesM e = case myParse $ pprint (e :: Exp) :: E.ParseResult E.Exp of+ E.ParseOk _ -> Nothing+ E.ParseFailed _ s -> Just s+++test_cycleAll = ioAll report_cycle+test_cycleBounded = ioBounded 10000 report_cycle+report_cycle e = case prop_cycle e of+ Nothing -> return ()+ Just (ee,ex) -> do+ putStrLn "Failure:"+ putStrLn (pprint ex)+ print ex+ putStrLn (E.prettyPrint ee)+ putStrLn ""++-- Round-trip property: TH -> String -> HSE -> TH+-- Uses haskell-src-meta for HSE -> TH+prop_cycle :: Exp -> Maybe (E.Exp,Exp)+prop_cycle e = case myParse $ pprint (e :: Exp) :: E.ParseResult E.Exp of+ E.ParseOk hse -> if e == toExp hse then Nothing else Just $ (hse, toExp hse)+ E.ParseFailed _ s -> Nothing -- Parse failures do not count as errors!++++-- Haskell parser+myParse :: String -> E.ParseResult E.Exp+myParse = E.parseWithMode E.defaultParseMode{E.extensions = + [ E.BangPatterns+ , E.ScopedTypeVariables+ , E.ViewPatterns+ , E.KindSignatures+ , E.ExplicitForAll+ , E.TypeFamilies+ ]}++++ +-- We define both SmallCheck and Feat enumerators for comparison. +c1 :: (Serial a, Enumerable a) => (a -> b) -> (Enumerate b, Series b)+c1 f = (unary f,cons1 f)+c0 f = (nullary f, cons0 f)++instance (Serial a, Serial b) => Serial (FreePair a b) where+ series = map Free . (series >< series) + coseries = undefined++toSel :: [(Enumerate b, Series b)] -> Enumerate b+toSel xs = consts $ map fst xs++toSerial :: [(Enumerate b, Series b)] -> Series b+toSerial xs = foldl1 (\/) $ map snd xs++++-- These statements are always expressions+newtype ExpStmt = ExpStmt Exp deriving Typeable++-- Declarations allowed in where clauses+newtype WhereDec = WhereDec{unWhere :: Dec} deriving Typeable++-- Lowecase names+newtype LcaseN = LcaseN {lcased :: Name} deriving Typeable+-- Uppercase names+newtype UpcaseName = UpcaseName {ucased :: Name} deriving Typeable+newtype BindN = BindN Name deriving Typeable+++instance (Enumerable a, Serial a) => Serial (NonEmpty a) where+ series = toSerial [c1 $ NonEmpty . funcurry (:)] + coseries = undefined + +instance Serial Nat where+ series = map (\(N a) -> Nat a) . series+ coseries = undefined +++newtype CPair a b = CPair {cPair :: (a,b)} deriving Typeable++instance (Enumerable a, Serial a,Enumerable b, Serial b) => Serial (CPair a b) where+ series = toSerial [c1 $ CPair . funcurry (,)] + coseries = undefined +instance (Serial a,Enumerable a,Enumerable b, Serial b) => Enumerable (CPair a b) where+ enumerate = toSel [c1 $ CPair . funcurry (,)] ++cExp = + [c1 $ VarE . lcased+ ,c1 $ ConE . ucased+ ,c1 LitE+ ,c1 $ funcurry AppE+ ,c1 $ \(ExpStmt a,o) -> InfixE (Just a) (either ConE VarE o) Nothing+ ,c1 $ \(ExpStmt a,o) -> InfixE Nothing (either ConE VarE o) (Just a)+ ,c1 $ \(a,o,b) -> InfixE (Just a) (either ConE VarE o) (Just b)+-- ,c1 $ funcurry $ funcurry $ \a o b -> UInfixE a (VarE o) b+-- ,c1 $ funcurry $ funcurry $ \a o b -> UInfixE a (ConE o) b +-- ,c1 ParensE+ ,c1 $ funcurry $ LamE . nonEmpty+ ,c1 $ \(x1,x2,xs) -> TupE (x1:x2:xs)+-- ,c1 UnboxedTupE+ ,c1 $ funcurry $ funcurry CondE+ ,c1 $ \(d,ds,e) -> LetE (map unWhere $ d:ds) e -- DISABLED BUGGY EMPTY LETS+ ,c1 $ \(e,NonEmpty m) -> CaseE e m+ ,c1 $ \(e,ss) -> DoE (ss ++ [NoBindS e])+ ,c1 $ (\((p,e),(CPair (xs,e'))) -> CompE ([BindS p e] ++ xs ++ [NoBindS e']))+-- ,c1 ArithSeqE -- BUGGY!+ ,c1 ListE+-- ,c1 $ funcurry SigE -- BUGGY!+ ,c1 $ \(e,x) -> RecConE e $ map unCase (nonEmpty x)+ ,c1 $ \(e,fe) -> RecUpdE e $ map unCase (nonEmpty fe)+ ]+instance Enumerable Exp where+ enumerate = toSel cExp+instance Serial Exp where+ series = toSerial cExp+ coseries = undefined++unCase (LcaseN n,e) = (n,e)++cExpStmt = + [ c1 $ ExpStmt . VarE+ , c1 $ ExpStmt . ConE+ , c1 $ ExpStmt . LitE+ , c1 $ \(e1,e2) -> ExpStmt (AppE e1 e2)+ , c1 $ ExpStmt . LitE+ -- , c1 parS+ -- Removed paralell comprehensions+ ]+instance Enumerable ExpStmt where+ enumerate = toSel cExpStmt+instance Serial ExpStmt where+ series = toSerial cExpStmt+ coseries = undefined+ +cPat = + [ c1 LitP+ , c1 $ \(BindN n) -> VarP n+ , c1 TupP + , c1 $ \(UpcaseName n,ps) -> ConP n ps+ , c1 $ \(p1,UpcaseName n,p2) -> InfixP p1 n p2+ , c1 TildeP+-- , c1 $ \(LcaseN n) -> BangP $ VarP n+ , c1 $ \(BindN n,p) -> AsP n p+ , c0 WildP+ , c1 $ \(UpcaseName e,x) -> RecP e (map (\(BindN n, p) -> (n,p)) (nonEmpty x))+ , c1 ListP+-- , c1 $ funcurry SigP -- BUGGY!+-- , c1 $ funcurry ViewP -- BUGGY!+ ]+instance Enumerable Pat where+ enumerate = toSel cPat+instance Serial Pat where+ series = toSerial cPat+ coseries = undefined++++-- deriveEnumerable ''Match -- Should remove decs+cMatch = + [c1 $ funcurry $ funcurry $ \x y ds -> Match x y (map unWhere ds)+ ]+instance Enumerable Match where+ enumerate = toSel cMatch+instance Serial Match where+ series = toSerial cMatch+ coseries = undefined + +cStmt = + [ c1 $ funcurry BindS+ , c1 $ \(d) -> LetS $ map unWhere $ nonEmpty d+ , c1 $ NoBindS+ -- , c1 parS+ -- Removed paralell comprehensions+ ]+instance Enumerable Stmt where+ enumerate = toSel cStmt+instance Serial Stmt where+ series = toSerial cStmt+ coseries = undefined+++cName = [ c1 (funcurry Name) ]+instance Enumerable Name where+ enumerate = toSel cName+instance Serial Name where+ series = toSerial cName+ coseries = undefined++cType = + [c1 $ funcurry $ funcurry $ (\(x) -> ForallT (nonEmpty x))+ ,c1 $ \(BindN a) -> VarT a+ ,c1 $ \(UpcaseName a) -> ConT a+ ,c1 $ \n -> TupleT (abs n)+ ,c0 ArrowT+ ,c0 ListT+ ,c1 $ funcurry AppT+ -- ,c1 $ funcurry SigT -- BUGGY!+ ]+instance Enumerable Type where+ enumerate = toSel cType+instance Serial Type where+ series = toSerial cType+ coseries = undefined+++-- deriveEnumerable ''Dec++cWhereDec = + [ c1 $ \(n,c) -> WhereDec $ FunD n (nonEmpty c)+ , c1 $ \(n,p,wds) -> WhereDec $ ValD n p (map unWhere wds)+ , c1 $ \(BindN a,b) -> WhereDec $ SigD a b+ -- , c1 $ WhereDec . PragmaD -- Removed pragmas+ -- , c1 parS -- Removed paralell comprehensions+ ]+instance Enumerable WhereDec where+ enumerate = toSel cWhereDec+instance Serial WhereDec where+ series = toSerial cWhereDec+ coseries = undefined+++ +cLit = + [ c1 StringL+ , c1 CharL -- TODO: Fair char generation+ , c1 $ IntegerL . nat+ -- , c1 RationalL -- BUGGY!+ -- Removed primitive litterals+ ]+instance Enumerable Lit where+ enumerate = toSel cLit+instance Serial Lit where+ series = toSerial cLit+ coseries = undefined+++cClause = + [c1 $ funcurry (funcurry $ \ps bs ds -> Clause ps bs (map unWhere ds))]+instance Enumerable Clause where+ enumerate = toSel cClause+instance Serial Clause where+ series = toSerial cClause+ coseries = undefined++++++-- deriveEnumerable ''Pred+cPred = + [ c1 $ funcurry ClassP+ , c1 $ funcurry EqualP+ ]+instance Enumerable Pred where+ enumerate = toSel cPred+instance Serial Pred where+ series = toSerial cPred+ coseries = undefined++-- deriveEnumerable ''TyVarBndr+cTyVarBndr = + [ c1 $ PlainTV+ , c1 $ funcurry KindedTV+ ]+instance Enumerable TyVarBndr where+ enumerate = toSel cTyVarBndr+instance Serial TyVarBndr where+ series = toSerial cTyVarBndr+ coseries = undefined+++cKind = + [c0 StarK+ ,c1 (funcurry ArrowK)+ ]+instance Enumerable Kind where+ enumerate = toSel cKind+instance Serial Kind where+ series = toSerial cKind+ coseries = undefined+++cBody =+ [ c1 NormalB+ , c1 $ \(x) -> GuardedB (nonEmpty x)+ -- Removed primitive litterals+ ]+instance Enumerable Body where+ enumerate = toSel cBody+instance Serial Body where+ series = toSerial cBody+ coseries = undefined++cGuard = + [c1 $ NormalG+ ,c1 $ \(s) -> PatG (nonEmpty s)+ ]+instance Enumerable Guard where+ enumerate = toSel cGuard+instance Serial Guard where+ series = toSerial cGuard+ coseries = undefined+ ++cCallconv = [c0 CCall, c0 StdCall]+instance Enumerable Callconv where+ enumerate = toSel cCallconv+instance Serial Callconv where+ series = toSerial cCallconv+ coseries = undefined+++cSafety = [c0 Unsafe, c0 Safe, c0 Interruptible]+instance Enumerable Safety where+ enumerate = toSel cSafety+instance Serial Safety where+ series = toSerial cSafety+ coseries = undefined+ ++cStrict = [c0 IsStrict, c0 NotStrict, c0 Unpacked]+instance Enumerable Strict where+ enumerate = toSel cStrict+instance Serial Strict where+ series = toSerial cStrict+ coseries = undefined++cInlineSpec = [c1 (funcurry $ funcurry $ InlineSpec)]+instance Enumerable InlineSpec where+ enumerate = toSel cInlineSpec+instance Serial InlineSpec where+ series = toSerial cInlineSpec+ coseries = undefined++cOccName = + [ c0 $ OccName "Con"+ , c0 $ OccName "var"+ ]+instance Enumerable OccName where+ enumerate = toSel cOccName+instance Serial OccName where+ series = toSerial cOccName+ coseries = undefined++cBindN = [c0 $ BindN $ Name (OccName "var") NameS]+instance Enumerable BindN where+ enumerate = toSel cBindN+instance Serial BindN where+ series = toSerial cBindN+ coseries = undefined+ +cLcaseN = [c1 $ \nf -> LcaseN $ Name (OccName "var") nf]+instance Enumerable LcaseN where+ enumerate = toSel cLcaseN+instance Serial LcaseN where+ series = toSerial cLcaseN+ coseries = undefined+ +cUpcaseName = [c1 $ \nf -> UpcaseName $ Name (OccName "Con") nf]+instance Serial UpcaseName where+ series = toSerial cUpcaseName+ coseries = undefined+instance Enumerable UpcaseName where+ enumerate = toSel cUpcaseName+ +cModName = [c0 $ ModName "M", c0 $ ModName "C.M"]+instance Enumerable ModName where+ enumerate = toSel cModName+instance Serial ModName where+ series = toSerial cModName+ coseries = undefined+ ++cRange = + [ c1 FromR+ , c1 (funcurry FromThenR)+ , c1 (funcurry FromToR)+ , c1 (funcurry $ funcurry FromThenToR)+ ]+instance Enumerable Range where+ enumerate = toSel cRange+instance Serial Range where+ series = toSerial cRange+ coseries = undefined++cNameFlavour = (+ [ c1 NameQ+-- , funcurry $ funcurry NameG +-- , \(I# x) -> NameU x+-- , \(I# x) -> NameL x+ , c0 NameS+ ])+instance Enumerable NameFlavour where+ enumerate = toSel cNameFlavour+instance Serial NameFlavour where+ series = toSerial cNameFlavour+ coseries = undefined++-- instance (Enumerable a, Integral a) => Enumerable (Ratio a) where+-- enumerate = consts [c1 $ funcurry (:%)]++
+ testing-feat.cabal view
@@ -0,0 +1,54 @@+Name: testing-feat+Version: 0.1+Synopsis: Functional enumeration for systematic and random testing+Description: Feat (Functional Enumeration of Abstract Types) + provides an enumeration as a function from natural + numbers to values (similar to @toEnum@). This can be used+ both for SmallCheck-style systematic testing and QuickCheck + style random testing, and hybrids of the two.+ .+ The enumerators are defined in a very boilerplate manner+ and there is a Template Haskell script for deriving the + class instance for most types.+ "Test.Feat" contain a subset of the other modules that + should be sufficient for most test usage. There + is a large scale example in the tar ball (testing the + Template Haskell pretty printer).+ +License: BSD3+License-file: LICENSE+Author: Jonas Duregård+Maintainer: jonas.duregard@gmail.com+Copyright: Jonas Duregård+Category: Testing+Build-type: Simple++Extra-source-files: + examples/TestTH.hs++Cabal-version: >=1.2++Library+ Hs-source-dirs: .+ Exposed-modules:+ Test.Feat, + Test.Feat.Access,+ Test.Feat.Class,+ Test.Feat.Enumerate,+ Test.Feat.Modifiers + Control.Monad.TagShare++ + Build-depends: + base >= 4.5 && <= 5,+ template-haskell >= 2.4 && < 2.8,+ mtl >= 1 && < 3,+ QuickCheck > 2 && < 3,+ containers < 1,+ data-memocombinators >= 0.4.2 && < 0.5+ + Other-modules:+ Test.Feat.Internals.Derive+ Test.Feat.Internals.Tag+ +