packages feed

quickcheck-arbitrary-adt-0.3.0.0: tests/Test/QuickCheck/Arbitrary/ADTSpec.hs

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell   #-}

module Test.QuickCheck.Arbitrary.ADTSpec (main, spec) where

-- base
import Data.Maybe (isJust)
import Data.Proxy
import GHC.Generics
-- hspec
import Test.Hspec
-- lens
import Control.Lens
-- QuickCheck
import Test.QuickCheck
-- quickcheck-adt-arbitrary
import Test.QuickCheck.Arbitrary.ADT

-- | A tagless type has constructor which has no parameters. It has U1 in its
-- `GHC.Generics` representation.
data TaglessType = TaglessType
  deriving (Eq,Generic,Show)

instance ToADTArbitrary TaglessType
instance Arbitrary TaglessType where
  arbitrary = genericArbitrary

$(makePrisms ''TaglessType)

-- | A sum type (disjoin union, tagged union) has multiple type constructors
-- that all result in values of different shapes, but the same type. It has
-- `:+:` and `M1 C c rep` in its `GHC.Generics` representation.
data SumType
  = SumType1 Int
  | SumType2 String Int
  | SumType3 String [Int] Double
  | SumType4 String [String] [Int] Double
  deriving (Eq,Generic,Show)

instance ToADTArbitrary SumType
instance Arbitrary SumType where
  arbitrary = genericArbitrary

$(makePrisms ''SumType)

-- | This type is an example of a sum type that has constructors building sum
-- types.
data SumOfSums
  = SSBareSumType TaglessType
  | SSSumType SumType
  deriving (Eq,Generic,Show)

instance ToADTArbitrary SumOfSums
instance Arbitrary SumOfSums where
  arbitrary = genericArbitrary

$(makePrisms ''SumOfSums)

-- | A product type has one constructor with one records. It has :*: and `M1 S s rep`
-- in its `GHC.Generics` representation.

data ProductType =
  ProductType
    { name :: String
    , age  :: Int
    } deriving (Eq,Generic,Show)

instance ToADTArbitrary ProductType

data PolymorphicParameterProductType a =
  PolymorphicParameterProductType
    { firstItem  :: Int
    , secondItem :: a
    } deriving (Eq,Show,Generic)

instance ToADTArbitrary (PolymorphicParameterProductType String)

data NonGenericSumType
  = NGSumType1 Int
  | NGSumType2 String Int
  | NGSumType3 String [Int] Double
  | NGSumType4 String [String] [Int] Double
  deriving (Eq, Show)

instance ToADTArbitrary NonGenericSumType where
  toADTArbitrarySingleton Proxy =
    ADTArbitrarySingleton "Test.QuickCheck.Arbitrary.ADTSpec" "NonGenericSum"
      <$> oneof
        [ ConstructorArbitraryPair "NGSumType1" <$> (NGSumType1 <$> arbitrary)
        , ConstructorArbitraryPair "NGSumType2" <$> (NGSumType2 <$> arbitrary <*> arbitrary)
        , ConstructorArbitraryPair "NGSumType3" <$> (NGSumType3 <$> arbitrary <*> arbitrary <*> arbitrary)
        , ConstructorArbitraryPair "NGSumType4" <$> (NGSumType4 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary)
        ]

  toADTArbitrary Proxy =
    ADTArbitrary "Test.QuickCheck.Arbitrary.ADTSpec" "NonGenericSum"
      <$> sequence
        [ ConstructorArbitraryPair "NGSumType1" <$> (NGSumType1 <$> arbitrary)
        , ConstructorArbitraryPair "NGSumType2" <$> (NGSumType2 <$> arbitrary <*> arbitrary)
        , ConstructorArbitraryPair "NGSumType3" <$> (NGSumType3 <$> arbitrary <*> arbitrary <*> arbitrary)
        , ConstructorArbitraryPair "NGSumType4" <$> (NGSumType4 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary)
        ]

$(makePrisms ''NonGenericSumType)

data NonGenericProductType =
  NonGenericProductType 
    { ngName :: String
    , ngAge  :: Int
    } deriving (Eq,Generic,Show)

instance ToADTArbitrary NonGenericProductType where
  toADTArbitrarySingleton Proxy =
    ADTArbitrarySingleton "Test.QuickCheck.Arbitrary.ADTSpec" "NonGenericSum"
      <$> (ConstructorArbitraryPair "NonGenericProductType" <$> (NonGenericProductType <$> arbitrary <*> arbitrary))

  toADTArbitrary Proxy =
    ADTArbitrary "Test.QuickCheck.Arbitrary.ADTSpec" "NonGenericProductType"
      <$> sequence
        [ ConstructorArbitraryPair "NonGenericProductType" <$> (NonGenericProductType <$> arbitrary <*> arbitrary)
        ]

spec :: Spec
spec = do
  describe "QuickCheck Arbitrary ADT: Generic ToADTArbitrary type class instance" $ do
    it "toADTArbitrary of a tagless type should create an instance of the bare constructor" $ do
      _                    <- generate (toADTArbitrarySingleton (Proxy :: Proxy TaglessType))
      taglessType          <- generate (toADTArbitrary (Proxy :: Proxy TaglessType))
      let taglessTypeArbitraries = capArbitrary <$> adtCAPs taglessType
      or (isJust . preview _TaglessType <$> taglessTypeArbitraries) `shouldBe` True

    it "toADTArbitrary of a sum type creates an instance with each constructor" $ do
      sumTypeCAP <- capArbitrary . adtasCAP <$> generate (toADTArbitrarySingleton (Proxy :: Proxy SumType))
      sumTypes   <- generate (toADTArbitrary (Proxy :: Proxy SumType))
      let sumTypeArbitraries = capArbitrary <$> adtCAPs sumTypes

      or
        [ isJust . preview _SumType1 $ sumTypeCAP
        , isJust . preview _SumType2 $ sumTypeCAP
        , isJust . preview _SumType3 $ sumTypeCAP
        , isJust . preview _SumType4 $ sumTypeCAP
        ] `shouldBe` True
        
      and
        [ or $ isJust . preview _SumType1 <$> sumTypeArbitraries
        , or $ isJust . preview _SumType2 <$> sumTypeArbitraries
        , or $ isJust . preview _SumType3 <$> sumTypeArbitraries
        , or $ isJust . preview _SumType4 <$> sumTypeArbitraries
        , length sumTypeArbitraries == 4
        ] `shouldBe` True

    it "toADTArbitrary of a sum of sum types creates an instance with each constructor of the top level" $ do
      _         <- generate (toADTArbitrarySingleton (Proxy :: Proxy SumOfSums))
      sumOfSums <- generate (toADTArbitrary (Proxy :: Proxy SumOfSums))
      let sumOfSumsArbitraries = capArbitrary <$> adtCAPs sumOfSums
      and
        [ or $ isJust . preview _SSBareSumType <$> sumOfSumsArbitraries
        , or $ isJust . preview _SSSumType <$> sumOfSumsArbitraries
        , length sumOfSumsArbitraries == 2
        ] `shouldBe` True

    it "toADTArbitrary of a product type creates a single instance" $ do
      _           <- generate (toADTArbitrarySingleton (Proxy :: Proxy ProductType))
      productType <- generate (toADTArbitrary (Proxy :: Proxy ProductType))
      let productTypeArbitraries = capArbitrary <$> adtCAPs productType

      length productTypeArbitraries `shouldBe` 1

    it "toADTArbitrary of a product type that has a polymorphic parameter should work as well" $ do
      _           <- generate (toADTArbitrarySingleton (Proxy :: Proxy (PolymorphicParameterProductType String)))
      productType <- generate (toADTArbitrary (Proxy :: Proxy (PolymorphicParameterProductType String)))
      let productTypeArbitraries = capArbitrary <$> adtCAPs productType

      length productTypeArbitraries `shouldBe` 1

  describe "QuickCheck Arbitrary ADT: ToADTArbitrary type class instance without Generics" $ do
    it "toADTArbitrary of a sum type creates an instance with each constructor" $ do
      sumTypeCAP <- capArbitrary . adtasCAP <$> generate (toADTArbitrarySingleton (Proxy :: Proxy NonGenericSumType))
      sumTypes   <- generate (toADTArbitrary (Proxy :: Proxy NonGenericSumType))
      let sumTypeArbitraries = capArbitrary <$> adtCAPs sumTypes

      or
        [ isJust . preview _NGSumType1 $ sumTypeCAP
        , isJust . preview _NGSumType2 $ sumTypeCAP
        , isJust . preview _NGSumType3 $ sumTypeCAP
        , isJust . preview _NGSumType4 $ sumTypeCAP
        ] `shouldBe` True

      and
        [ or $ isJust . preview _NGSumType1 <$> sumTypeArbitraries
        , or $ isJust . preview _NGSumType2 <$> sumTypeArbitraries
        , or $ isJust . preview _NGSumType3 <$> sumTypeArbitraries
        , or $ isJust . preview _NGSumType4 <$> sumTypeArbitraries
        , length sumTypeArbitraries == 4
        ] `shouldBe` True

    it "toADTArbitrary of a product type creates a single instance" $ do
      _           <- generate (toADTArbitrarySingleton (Proxy :: Proxy NonGenericProductType))
      productType <- generate (toADTArbitrary (Proxy :: Proxy NonGenericProductType))
      let productTypeArbitraries = capArbitrary <$> adtCAPs productType

      length productTypeArbitraries `shouldBe` 1

main :: IO ()
main = hspec spec