packages feed

binary-tagged-0.3: test/Tests.hs

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE DeriveDataTypeable  #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE KindSignatures      #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Main (main) where

import Data.Binary               (Binary, decode, encode)
import Data.Coerce               (coerce)
import Data.Either               (isLeft)
import Data.Functor.Identity     (Identity (..))
import Data.Monoid               (Product (..), Sum (..))
import Data.Proxy                (Proxy (..))
import Data.Singletons.Bool      (SBoolI, reflectBool)
import Data.Typeable             (Typeable)
import GHC.Generics              (Generic)
import Test.QuickCheck
       (Arbitrary (..), Property, counterexample, ioProperty, property, (===))
import Test.QuickCheck.Instances ()
import Test.Tasty                (TestTree, defaultMain, testGroup)
import Test.Tasty.HUnit          (testCase, (@?=))
import Test.Tasty.QuickCheck     (testProperty)

import Data.Binary.Tagged

import qualified Rec1
import qualified Rec2

main :: IO ()
main = defaultMain $ testGroup "Tests"
  [ roundtrips
  , wrongRoundtrips
  , failedRoundtrips
  , testCase "An example hash" $ do
    let hash = structureHash (Proxy :: Proxy [Either (Maybe Char) (Sum Int)])
    hash @?= md5FromInteger 0xbfc698fe96db703b44e5925381902ef2
  ]

proxyRec1 :: Proxy Rec1.Rec
proxyRec1 = Proxy

proxyRec2 :: Proxy Rec2.Rec
proxyRec2 = Proxy

proxyRec1Ver0 :: Proxy (VerTagged 'False Rec1.Rec)
proxyRec1Ver0 = Proxy

proxyRec1Ver1 :: Proxy (VerTagged 'True Rec1.Rec)
proxyRec1Ver1 = Proxy

proxyRec2Ver0 :: Proxy (VerTagged 'False Rec2.Rec)
proxyRec2Ver0 = Proxy

-- proxyRec2Ver1 :: Proxy (VerTagged 'True Rec2.Rec)
-- proxyRec2Ver1 = Proxy

eqRec1Rec2 :: Rec1.Rec -> Rec2.Rec -> Bool
eqRec1Rec2 (Rec1.Rec (Sum a) (Product b)) (Rec2.Rec (Product a') (Sum b')) =
  a == a' && b == b'

roundtrips :: TestTree
roundtrips = testGroup "Roundtrip"
  [ testProperty "Rec1"                $ roundtrip proxyRec1
  , testProperty "Rec2"                $ roundtrip proxyRec2
  ]

wrongRoundtrips :: TestTree
wrongRoundtrips = testGroup "Decode successful, data invalid"
  [ testProperty "Rec1 -> Rec2" $ wrongRoundtrip eqRec1Rec2
  , testProperty "Rec2 -> Rec1" $ wrongRoundtrip eqRec1Rec2
  ]

failedRoundtrips :: TestTree
failedRoundtrips = testGroup "Failed roundtrips"
  [ testProperty "Different version" $ failedRoundtrip proxyRec1Ver0 proxyRec1Ver1
  , testProperty "Different structure" $ failedRoundtrip proxyRec1Ver0 proxyRec2Ver0
  ]

roundtrip :: (Eq a, Show a, Arbitrary a, Binary a, Structured a) => Proxy a -> a -> Property
roundtrip _ x = x === structuredDecode (structuredEncode x)

wrongRoundtrip :: (Arbitrary a, Binary a, Binary b, Structured a, Structured b) => (a -> b -> Bool) -> a -> Property
wrongRoundtrip eq x = property $ eq x $ decode (encode x)

isLeftProperty :: (Show a, Show b) => Either a b -> Property
isLeftProperty x = counterexample ("not isLeft: " ++ show x) (isLeft x)

failedRoundtrip :: forall a b. (Arbitrary a, Binary a, Binary b, Structured a, Structured b, Show b) => Proxy a -> Proxy b -> a -> Property
failedRoundtrip _ _ x = ioProperty $ do
    x' <- structuredDecodeOrFailIO (structuredEncode x) :: IO (Either String b)
    return (isLeftProperty x')

-------------------------------------------------------------------------------
-- VerTagged
-------------------------------------------------------------------------------

newtype VerTagged (v :: Bool) a = VT a deriving (Eq, Show, Generic, Typeable)

instance Binary a => Binary (VerTagged v a)

instance (SBoolI v, Typeable v, Structured a) => Structured (VerTagged v a) where
    structure = set typeVersion (fromBool $ reflectBool (Proxy :: Proxy v)) . genericStructure

fromBool :: Integral a => Bool -> a
fromBool True  = 1
fromBool False = 0

instance Arbitrary a => Arbitrary (VerTagged v a) where
    arbitrary     = fmap VT arbitrary
    shrink (VT x) = fmap VT (shrink x)

-------------------------------------------------------------------------------
-- Lens
-------------------------------------------------------------------------------

over :: ((a -> Identity b) -> s -> Identity t) -> (a -> b) -> s -> t
over = coerce

set :: ((a -> Identity b) -> s -> Identity t) -> b -> s -> t
set l b = over l (const b)