{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} -- | -- Module : Haskoin.Test.Util -- Copyright : No rights reserved -- License : MIT -- Maintainer : jprupp@protonmail.ch -- Stability : experimental -- Portability : POSIX module Haskoin.Util.Arbitrary.Util ( arbitraryBS, arbitraryBS1, arbitraryBSn, arbitraryBSS, arbitraryBSS1, arbitraryBSSn, arbitraryMaybe, arbitraryNetwork, arbitraryUTCTime, ReadBox (..), JsonBox (..), MarshalJsonBox (..), SerialBox (..), MarshalBox (..), IdentityTests (..), testIdentity, testSerial, testRead, testJson, ) where import Control.Monad (forM_, (<=<)) import qualified Data.Aeson as A import qualified Data.Aeson.Encoding as A import qualified Data.Aeson.Types as A import Data.ByteString (ByteString, pack) import Data.ByteString.Lazy (fromStrict, toStrict) import qualified Data.ByteString.Short as BSS import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial import Data.Default import qualified Data.Map.Strict as Map import Data.Proxy import Data.Time.Clock (UTCTime (..)) import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import qualified Data.Typeable as T import Data.Word (Word32) import Haskoin.Crypto (Ctx) import Haskoin.Network.Constants import Haskoin.Network.Data import Haskoin.Util import Test.Hspec (Spec, describe, shouldBe, shouldSatisfy) import Test.Hspec.QuickCheck (prop) import Test.QuickCheck -- | Arbitrary strict 'ByteString'. arbitraryBS :: Gen ByteString arbitraryBS = pack <$> arbitrary -- | Arbitrary non-empty strict 'ByteString' arbitraryBS1 :: Gen ByteString arbitraryBS1 = pack <$> listOf1 arbitrary -- | Arbitrary strict 'ByteString' of a given length arbitraryBSn :: Int -> Gen ByteString arbitraryBSn n = pack <$> vectorOf n arbitrary -- | Arbitrary 'ShortByteString'. arbitraryBSS :: Gen BSS.ShortByteString arbitraryBSS = BSS.pack <$> arbitrary -- | Arbitrary non-empty 'ShortByteString' arbitraryBSS1 :: Gen BSS.ShortByteString arbitraryBSS1 = BSS.pack <$> listOf1 arbitrary -- | Arbitrary 'ShortByteString' of a given length arbitraryBSSn :: Int -> Gen BSS.ShortByteString arbitraryBSSn n = BSS.pack <$> vectorOf n arbitrary -- | Arbitrary UTCTime that generates dates after 01 Jan 1970 01:00:00 CET arbitraryUTCTime :: Gen UTCTime arbitraryUTCTime = do w <- arbitrary :: Gen Word32 return $ posixSecondsToUTCTime $ realToFrac w -- | Generate a Maybe from a Gen a arbitraryMaybe :: Gen a -> Gen (Maybe a) arbitraryMaybe g = frequency [ (1, return Nothing), (5, Just <$> g) ] -- | Generate an Network arbitraryNetwork :: Gen Network arbitraryNetwork = elements allNets -- Helpers for creating Serial and JSON Identity tests instance Show Ctx where show _ = "Ctx" data ReadBox = forall a. (Read a, Show a, Eq a, T.Typeable a) => ReadBox (Gen a) data JsonBox = forall a. (Show a, Eq a, T.Typeable a, A.ToJSON a, A.FromJSON a) => JsonBox (Gen a) data MarshalJsonBox = forall s a. (Show a, Show s, Eq a, T.Typeable a, MarshalJSON s a) => MarshalJsonBox (Gen (s, a)) data SerialBox = forall a. (Show a, Eq a, T.Typeable a, Serial a) => SerialBox (Gen a) data MarshalBox = forall s a. (Show a, Show s, Eq a, T.Typeable a, Marshal s a) => MarshalBox (Gen (s, a)) data IdentityTests = IdentityTests { readTests :: [ReadBox], jsonTests :: [JsonBox], marshalJsonTests :: [MarshalJsonBox], serialTests :: [SerialBox], marshalTests :: [MarshalBox] } instance Default IdentityTests where def = IdentityTests { readTests = [], jsonTests = [], marshalJsonTests = [], serialTests = [], marshalTests = [] } testIdentity :: IdentityTests -> Spec testIdentity t = do describe "Read/Show Encoding" $ forM_ t.readTests $ \(ReadBox g) -> testRead g describe "Data.Aeson Encoding" $ forM_ t.jsonTests $ \(JsonBox g) -> testJson g describe "MarshalJSON Encoding" $ forM_ t.marshalJsonTests $ \(MarshalJsonBox g) -> testMarshalJson g describe "Binary Encoding" $ forM_ t.serialTests $ \(SerialBox g) -> testSerial g describe "Marshal Encoding" $ forM_ t.marshalTests $ \(MarshalBox g) -> testMarshal g -- | Generate Read/Show identity tests testRead :: (Eq a, Read a, Show a, T.Typeable a) => Gen a -> Spec testRead gen = prop ("read/show identity for " <> name) $ forAll gen $ \x -> (read . show) x `shouldBe` x where name = show $ T.typeRep $ proxy gen proxy :: Gen a -> Proxy a proxy = const Proxy -- | Generate binary identity tests testSerial :: (Eq a, Show a, T.Typeable a, Serial a) => Gen a -> Spec testSerial gen = prop ("Binary encoding/decoding identity for " <> name) $ forAll gen $ \x -> do (runGetL deserialize . runPutL . serialize) x `shouldBe` x (runGetL deserialize . fromStrict . runPutS . serialize) x `shouldBe` x (runGetS deserialize . runPutS . serialize) x `shouldBe` Right x (runGetS deserialize . toStrict . runPutL . serialize) x `shouldBe` Right x where name = show $ T.typeRep $ proxy gen proxy :: Gen a -> Proxy a proxy = const Proxy -- | Generate Marshal identity tests testMarshal :: (Eq a, Show a, Show s, T.Typeable a, Marshal s a) => Gen (s, a) -> Spec testMarshal gen = do prop ("Marshal marshalPut/marshalGet identity for " <> name) $ forAll gen $ \(s, a) -> do (unmarshal s . marshal s) a `shouldBe` Right a (unmarshalLazy s . marshalLazy s) a `shouldBe` a where name = show $ T.typeRep $ proxy gen proxy :: Gen (s, a) -> Proxy a proxy = const Proxy -- | Generate Data.Aeson identity tests testJson :: (Eq a, Show a, T.Typeable a, A.ToJSON a, A.FromJSON a) => Gen a -> Spec testJson gen = do prop ("Data.Aeson toJSON/fromJSON identity for " <> name) $ forAll gen (`shouldSatisfy` jsonID) prop ("Data.Aeson toEncoding/fromJSON identity for " <> name) $ forAll gen (`shouldSatisfy` encodingID) where name = show $ T.typeRep $ proxy gen proxy :: Gen a -> Proxy a proxy = const Proxy jsonID x = (A.fromJSON . A.toJSON) (toMap x) == A.Success (toMap x) encodingID x = (A.decode . A.encodingToLazyByteString . A.toEncoding) (toMap x) == Just (toMap x) -- | Generate MarshalJSON identity tests testMarshalJson :: (Eq a, Show a, Show s, T.Typeable a, MarshalJSON s a) => Gen (s, a) -> Spec testMarshalJson gen = do prop ("MarshalJSON marshalValue/unmarshalValue identity for " <> name) $ forAll gen $ \(s, a) -> a `shouldSatisfy` marshalJsonID s prop ("MarshalJSON marshalEncoding/unmarshalValue identity for " <> name) $ forAll gen $ \(s, a) -> a `shouldSatisfy` marshalEncodingID s where name = show $ T.typeRep $ proxy gen proxy :: Gen (s, a) -> Proxy a proxy = const Proxy marshalJsonID s a = A.parseMaybe (unmarshalValue s) (marshalValue s a) == Just a marshalEncodingID s a = unmarshalJSON s (marshalJSON s a) == Just a toMap :: a -> Map.Map String a toMap = Map.singleton "object" toMapE :: A.Encoding -> A.Encoding toMapE = A.pairs . A.pair "object" fromMap :: Map.Map String a -> a fromMap = (Map.! "object")