packages feed

binary-typed-0.1.0.0: tests/QuickCheck.hs

{-# LANGUAGE NumDecimals #-}

module QuickCheck (props) where


import Control.Applicative
import Data.Typeable (Typeable)

import Data.Binary
import Data.Binary.Typed
import Data.Binary.Typed.Internal

import Test.Tasty
import Test.Tasty.QuickCheck
import Text.Show.Functions () -- This fixes the missing Show (a->b) instance in
                              -- Travis. Can probably be removed in the future.



-- | The entire QuickCheck test tree, to be imported qualified
props :: TestTree
props = tree tests where
      tree = testGroup "QuickCheck"
      tests = [ prop_typerep
              , prop_inverses
              , prop_api
              , prop_internal
              ]



-- | Check whether typed encoding and decoding are inverses of each other
prop_inverses :: TestTree
prop_inverses = tree tests where
      tree = testGroup "decode.encode = id"
      tests = [ prop_inverses_int
              , prop_inverses_string
              ]



-- | Check whether encoding and decoding an Int works properly
prop_inverses_int :: TestTree
prop_inverses_int = tree tests where

      tree = localOption (QuickCheckMaxSize maxBound)
           . localOption (QuickCheckTests 1e3)
           . testGroup "Int"

      tests = [ testProperty "Untyped" (prop Untyped)
              , testProperty "Hashed"  (prop Hashed)
              , testProperty "Shown"   (prop Shown)
              , testProperty "Full"    (prop Full)
              , testProperty "Cached"   prop_cached
              ]

      prop :: TypeFormat -> Int -> Bool
      prop format i = unsafeDecodeTyped (encodeTyped format i) == i

      prop_cached :: Typed Int -> Int -> Bool
      prop_cached dummy i = unsafeDecodeTyped (encodeTypedLike dummy i) == i



-- | Check whether encoding and decoding a String works properly
prop_inverses_string :: TestTree
prop_inverses_string = tree tests where

      tree = localOption (QuickCheckMaxSize 100)
           . localOption (QuickCheckTests 1e3)
           . testGroup "String"

      tests = [ testProperty "Untyped" (prop Untyped)
              , testProperty "Hashed"  (prop Hashed)
              , testProperty "Shown"   (prop Shown)
              , testProperty "Full"    (prop Full)
              , testProperty "Cached"   prop_cached
              ]

      prop :: TypeFormat -> String -> Bool
      prop format i = unsafeDecodeTyped (encodeTyped format i) == i

      prop_cached :: Typed String -> String -> Bool
      prop_cached dummy i = unsafeDecodeTyped (encodeTypedLike dummy i) == i



-- | Test properties of 'TypeRep's and 'TyCon's.
prop_typerep :: TestTree
prop_typerep = tree tests where
      tree = localOption (QuickCheckTests 1e3)
           . localOption (QuickCheckMaxSize 10)
           . testGroup "TypeRep, TyCon"

      tests = [ prop_hash_total
              ]



-- | Generate lots of hashes from random 'typeRep's and see whether one of them
--   crashes.
prop_hash_total :: TestTree
prop_hash_total = testProperty "Hash function total" prop where
      prop = forAll arbitrary
                    (\tyCon -> hashType (unStripTypeRep tyCon) `seq` True)



instance Arbitrary TyCon where
      arbitrary = TyCon <$> arbitrary <*> arbitrary <*> arbitrary

instance Arbitrary TypeRep where
      arbitrary = TypeRep <$> arbitrary <*> args
            where args = listOf (modifySize (`div` 2) arbitrary)



-- | Modify the size parameter of a 'Gen'.
modifySize :: (Int -> Int) -> Gen a -> Gen a
modifySize f gen = sized (\n -> resize (f n) gen)



-- | Check whether the laws mentioned in the docs hold
prop_api :: TestTree
prop_api = tree tests where

      tree = testGroup "API"

      tests = [ testProperty "erase"            prop_erase
              , testProperty "mapTyped id law"  prop_mapTyped_id
              , testProperty "mapTyped f.g law" prop_mapTyped_compose
              , testProperty "reType"           prop_reType
              , testProperty "encodeTyped"      prop_encodeTyped
              , testProperty "encodeTypedLike"  prop_encodeTypedLike
              ]

      prop_erase :: TypeFormat -> Int -> Bool
      prop_erase format x = erase (typed format x) == x

      prop_mapTyped_id :: Typed Double -> Bool
      prop_mapTyped_id x = x `isEqual` mapTyped id x

      prop_mapTyped_compose
            :: (Int -> Maybe Integer)
            -> (Double -> Int)
            -> Typed Double
            -> Bool
      prop_mapTyped_compose f g x =
            mapTyped (f . g) x `isIdentical` (mapTyped f . mapTyped g) x

      prop_reType :: TypeFormat -> Typed Int -> Bool
      prop_reType format x =reType format x `isIdentical` typed format (erase x)

      prop_encodeTyped :: TypeFormat -> Int -> Bool
      prop_encodeTyped format value =
            encodeTyped format value == encode (typed format value)

      prop_encodeTypedLike :: Typed Int -> Int -> Bool
      prop_encodeTypedLike ty value =
            (unsafeDecodeTyped (encodeTypedLike ty value) :: Int)
            ==
            unsafeDecodeTyped (encode (reValue (const value) ty))



-- | Equality of 'Typed' values, taking only the contained value into account.
--   See also 'isIdentical'.
isEqual :: Eq a => Typed a -> Typed a -> Bool
isEqual (Typed _tyA a) (Typed _tyB b) = a == b



-- | Equality of 'Typed' values, taking the contained type representation into
--   account. This means that a cached and an uncached (otherwise identical)
--   type representation are unequal.
--   See also 'isEqual'.
isIdentical :: Eq a => Typed a -> Typed a -> Bool
isIdentical (Typed tyA a) (Typed tyB b) = (tyA, a) == (tyB, b)



instance (Arbitrary a, Typeable a) => Arbitrary (Typed a) where
      arbitrary = frequency [(10, plain), (5, cached), (3, cached2)]
            where plain = typed <$> arbitrary <*> arbitrary
                  cached  = fmap precache plain
                  cached2 = fmap precache cached

instance Arbitrary TypeFormat where
      arbitrary = elements [Untyped, Hashed, Shown, Full]



prop_internal :: TestTree
prop_internal = tree tests where

      tree = testGroup "Internal"

      tests = [ testProperty "getFormat" prop_getFormat
              ]

      -- getFormat extracts the right format
      prop_getFormat :: Typed Double -> Bool
      prop_getFormat t@(Typed ty x) = t `isEqual` typed (getFormat ty) x