genvalidity-hspec-0.1.0.1: src/Data/GenValidity/Hspec.hs
{-# LANGUAGE MultiParamTypeClasses #-}
module Data.GenValidity.Hspec
( module Data.GenValidity
-- * Tests for GenValidity instances
, proxy
, genspec
, arbitrarySpec
, validitySpec
-- * Standard tests involving validity
, producesValidsOnGen
, alwaysProducesValid
, producesValidsOnValids
, producesValidsOnGens2
, alwaysProducesValid2
, producesValidsOnValids2
-- * Standard tests involving functions that can fail
, CanFail(..)
, succeedsOnGen
, succeedsOnValidInput
, failsOnGen
, failsOnInvalidInput
, validIfSucceedsOnGen
, validIfSucceeds
) where
import Data.GenValidity
import Test.Hspec
import Test.QuickCheck
import Data.Data
-- | A value of arbitrary type, used to specify which type to generate a spec
-- for.
proxy :: a
proxy = undefined
-- | A combination of @arbitrarySpec@ and @validitySpec@
--
-- Example usage:
--
-- > genspec (proxy :: MyData)
genspec
:: (Show a, Eq a, Data a, GenValidity a, Arbitrary a)
=> a
-> Spec
genspec proxy = do
let name = show $ typeOf proxy
describe ("GenSpec for " ++ name) $ do
arbitrarySpec proxy
validitySpec proxy
-- | A @Spec@ that specifies that @arbitrary@ only generates data that
-- satisfy @isValid@ and that @shrink@ only produces data that satisfy
-- @isValid@.
--
-- Example usage:
--
-- > arbitrarySpec (proxy :: MyData)
arbitrarySpec
:: (Typeable a, Show a, Eq a, Data a, GenValidity a, Arbitrary a)
=> a
-> Spec
arbitrarySpec proxy = do
let name = show $ typeOf proxy
describe ("Arbitrary " ++ name) $ do
it ("is instantiated such that 'arbitrary' only generates valid \'"
++ name
++ "\'s") $ do
forAll arbitrary $ \a ->
(a `asTypeOf` proxy) `shouldSatisfy` isValid
it ("is instantiated such that 'shrink' only produces valid \'"
++ name
++ "\'s") $ do
forAll arbitrary $ \a ->
shrink (a `asTypeOf` proxy) `shouldSatisfy` all isValid
-- | A @Spec@ that specifies that @genValid@ only generates valid data and that
-- @genInvalid@ only generates invalid data.
--
-- In general it is a good idea to add this spec to your test suite if you
-- write a custom implementation of @genValid@ or @genInvalid@.
--
-- Example usage:
--
-- > validitySpec (proxy :: MyData)
validitySpec
:: (Typeable a, Show a, Eq a, Data a, GenValidity a, Arbitrary a)
=> a
-> Spec
validitySpec proxy = do
let name = show $ typeOf proxy
describe "genValid" $ do
it ("only generates valid \'" ++ name ++ "\'s") $ do
forAll genValid $ \a ->
(a `asTypeOf` proxy) `shouldSatisfy` isValid
describe "genInvalid" $ do
it ("only generates invalid \'" ++ name ++ "\'s") $ do
forAll genInvalid $ \a ->
(a `asTypeOf` proxy) `shouldNotSatisfy` isValid
-- | A class of types that are the result of functions that can fail
--
-- You should not use this class yourself.
class CanFail f where
hasFailed :: f a -> Bool
resultIfSucceeded :: f a -> Maybe a
instance CanFail Maybe where
hasFailed Nothing = True
hasFailed _ = False
resultIfSucceeded Nothing = Nothing
resultIfSucceeded (Just r) = Just r
instance CanFail (Either e) where
hasFailed (Left _) = True
hasFailed _ = False
resultIfSucceeded (Left _) = Nothing
resultIfSucceeded (Right r) = Just r
-- | The function produces valid output when the input is generated as
-- specified by the given generator.
producesValidsOnGen
:: (Show a, Show b, Validity b)
=> (a -> b)
-> Gen a
-> Property
producesValidsOnGen func gen
= forAll gen $ \a -> func a `shouldSatisfy` isValid
-- | The function produces valid output when the input is generated by
-- @genUnchecked@
alwaysProducesValid
:: (Show a, Show b, GenValidity a, Validity b)
=> (a -> b)
-> Property
alwaysProducesValid = (`producesValidsOnGen` genUnchecked)
-- | The function produces valid output when the input is generated by
-- @genValid@
producesValidsOnValids
:: (Show a, Show b, GenValidity a, Validity b)
=> (a -> b)
-> Property
producesValidsOnValids = (`producesValidsOnGen` genValid)
producesValidsOnGens2
:: (Show a, Show b, Show c, Validity c)
=> (a -> b -> c)
-> Gen a -> Gen b
-> Property
producesValidsOnGens2 func gen1 gen2
= forAll gen1 $ \a ->
forAll gen2 $ \b ->
func a b `shouldSatisfy` isValid
alwaysProducesValid2
:: (Show a, Show b, Show c, GenValidity a, GenValidity b, Validity c)
=> (a -> b -> c)
-> Property
alwaysProducesValid2 func
= producesValidsOnGens2 func genUnchecked genUnchecked
producesValidsOnValids2
:: (Show a, Show b, Show c, GenValidity a, GenValidity b, Validity c)
=> (a -> b -> c)
-> Property
producesValidsOnValids2 func
= producesValidsOnGens2 func genValid genValid
-- | The function succeeds if the input is generated by the given generator
succeedsOnGen
:: (Show a, Show b, Show (f b), CanFail f)
=> (a -> f b)
-> Gen a
-> Property
succeedsOnGen func gen
= forAll gen $ \a -> func a `shouldNotSatisfy` hasFailed
-- | The function succeeds if the input is generated by @genValid@
succeedsOnValidInput
:: (Show a, Show b, Show (f b), GenValidity a, CanFail f)
=> (a -> f b)
-> Property
succeedsOnValidInput = (`succeedsOnGen` genValid)
-- | The function fails if the input is generated by the given generator
failsOnGen
:: (Show a, Show b, Show (f b), CanFail f)
=> (a -> f b)
-> Gen a
-> Property
failsOnGen func gen
= forAll gen $ \a -> func a `shouldSatisfy` hasFailed
-- | The function fails if the input is generated by @genInvalid@
failsOnInvalidInput
:: (Show a, Show b, Show (f b), GenValidity a, CanFail f)
=> (a -> f b)
-> Property
failsOnInvalidInput = (`failsOnGen` genInvalid)
-- | The function produces output that satisfies @isValid@ if it is given input
-- that is generated by the given generator.
validIfSucceedsOnGen
:: (Show a, Show b, Show (f b), Validity b, CanFail f)
=> (a -> f b)
-> Gen a
-> Property
validIfSucceedsOnGen func gen
= forAll gen $ \a ->
case resultIfSucceeded (func a) of
Nothing -> return () -- Can happen
Just res -> res `shouldSatisfy` isValid
-- | The function produces output that satisfies @isValid@ if it is given input
-- that is generated by @genUnchecked@.
validIfSucceeds
:: (Show a, Show b, Show (f b), GenValidity a, Validity b, CanFail f)
=> (a -> f b)
-> Property
validIfSucceeds = (`validIfSucceedsOnGen` genUnchecked)