Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
138 lines (111 sloc) 4.75 KB
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Generate fake values with full constructor coverage
--
-- The idea behind Fake.Cover is that although exhaustive testing is highly
-- exponential, you can cover a large portion of the likely problem cases by
-- exercising all the constructors of a data type and associated fields. This
-- approach only requires a sub-exponential number of cases--far fewer than what
-- you need for the exhaustive approach. The number of test cases needed to
-- ensure that you have full coverage of all the constructors is given by the
-- following relations for product and sum types:
--
-- numCases (a, b) = max (numCases a) (numCases b)
--
-- numCases (Either a b) = numCases a + numCases b
--
-- See the test suite for examples of how many values are generated for
-- different data types.
module Fake.Cover
( gcover
, Coverage(..)
, Cover(..)
) where
------------------------------------------------------------------------------
import Control.Applicative
import GHC.Generics as G
------------------------------------------------------------------------------
import Fake.Types
------------------------------------------------------------------------------
------------------------------------------------------------------------------
-- | Coverage is a list of values, implemented here with a newtype around a
-- list of fake value generators. It's @[FGen a]@ instead of @FGen [a]@
-- because we don't want to have to evaluate the 'FGen' monad to work with
-- coverage lists.
newtype Coverage a = Coverage { unCoverage :: [FGen a] }
deriving (Functor)
instance Applicative Coverage where
pure = Coverage . pure . pure
Coverage as <*> Coverage bs = Coverage $ zipWith (<*>)
(as ++ take (newlen - alen) (cycle as))
(bs ++ take (newlen - blen) (cycle bs))
where
alen = length as
blen = length bs
newlen = max alen blen
instance Alternative Coverage where
empty = Coverage empty
Coverage as <|> Coverage bs = Coverage (as ++ bs)
------------------------------------------------------------------------------
-- | A type class that generates a list of values giving full construcor
-- coverage for data types. You can write your own instances by hand or you
-- can use the default instance which calls 'gcover' provided your data type
-- has a Generic instance.
class Cover a where
cover :: Coverage a
default cover :: (Generic a, GCover ga, ga ~ G.Rep a) => Coverage a
cover = gcover
instance Cover () where
cover = gcover
instance Cover a => Cover (Maybe a) where
cover = gcover
instance (Cover a, Cover b) => Cover (Either a b) where
cover = gcover
instance (Cover a, Cover b) => Cover (a,b) where
cover = gcover
instance (Cover a, Cover b, Cover c) => Cover (a,b,c) where
cover = gcover
instance (Cover a, Cover b, Cover c, Cover d) => Cover (a,b,c,d) where
cover = gcover
instance (Cover a, Cover b, Cover c, Cover d, Cover e)
=> Cover (a,b,c,d,e) where
cover = gcover
instance (Cover a, Cover b, Cover c, Cover d, Cover e, Cover f)
=> Cover (a,b,c,d,e,f) where
cover = gcover
instance (Cover a, Cover b, Cover c, Cover d, Cover e, Cover f, Cover g)
=> Cover (a,b,c,d,e,f,g) where
cover = gcover
-- GHC only has Generic instances up to 7-tuples
------------------------------------------------------------------------------
-- | A generic function that gives you full constructor coverage for a data
-- type. Using this function as the 'Cover' instance for a data type avoids
-- the need to explicitly enumerate values that include coverage of all
-- constructors.
gcover :: (Generic a, GCover ga, ga ~ G.Rep a) => Coverage a
gcover = Coverage $ fmap G.to <$> genericCover
------------------------------------------------------------------------------
-- | Used to implement 'gcover'.
class GCover a where
genericCover :: [FGen (a x)]
instance GCover G.U1 where
genericCover = pure $ pure G.U1
instance Cover c => GCover (G.K1 i c) where
genericCover = fmap G.K1 <$> unCoverage cover
instance GCover f => GCover (G.M1 i c f) where
genericCover = fmap G.M1 <$> genericCover
instance (GCover a, GCover b) => GCover (a G.:*: b) where
genericCover = unCoverage $
(G.:*:) <$> Coverage genericCover <*> Coverage genericCover
instance (GCover a, GCover b) => GCover (a G.:+: b) where
genericCover = unCoverage $
(G.L1 <$> Coverage genericCover) <|> (G.R1 <$> Coverage genericCover)