packages feed

haskoin-core-0.14.1: src/Haskoin/Util/Arbitrary/Util.hs

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings         #-}
{-|
Module      : Haskoin.Test.Util
Copyright   : No rights reserved
License     : UNLICENSE
Maintainer  : jprupp@protonmail.ch
Stability   : experimental
Portability : POSIX
-}
module Haskoin.Util.Arbitrary.Util
( arbitraryBS
, arbitraryBS1
, arbitraryBSn
, arbitraryBSS
, arbitraryBSS1
, arbitraryBSSn
, arbitraryMaybe
, arbitraryNetwork
, SerialBox(..)
, JsonBox(..)
, NetBox(..)
, ReadBox(..)
, testIdentity
, testSerial
, testRead
, testJson
, testNetJson
, arbitraryNetData
, genNetData
)
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 qualified Data.ByteString.Short as BSS
import qualified Data.Map.Strict       as Map
import           Data.Proxy
import qualified Data.Serialize        as S
import           Data.Time.Clock       (UTCTime (..))
import           Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import qualified Data.Typeable         as T
import           Data.Word             (Word32)
import           Haskoin.Constants
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

data SerialBox =
    forall a. (Show a, Eq a, T.Typeable a, S.Serialize a) =>
              SerialBox (Gen a)

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 NetBox =
    forall a. (Show a, Eq a, T.Typeable a) =>
              NetBox
                  ( Network -> a -> A.Value
                  , Network -> a -> A.Encoding
                  , Network -> A.Value -> A.Parser a
                  , Gen (Network, a))

testIdentity :: [SerialBox] -> [ReadBox] -> [JsonBox] -> [NetBox] -> Spec
testIdentity serialVals readVals jsonVals netVals = do
    describe "Data.Serialize Encoding" $
        forM_ serialVals $ \(SerialBox g) -> testSerial g
    describe "Read/Show Encoding" $
        forM_ readVals $ \(ReadBox g) -> testRead g
    describe "Data.Aeson Encoding" $
        forM_ jsonVals $ \(JsonBox g) -> testJson g
    describe "Data.Aeson Encoding with Network" $
        forM_ netVals $ \(NetBox (j,e,p,g)) -> testNetJson j e p g

-- | Generate Data.Serialize identity tests
testSerial ::
       (Eq a, Show a, T.Typeable a, S.Serialize a) => Gen a -> Spec
testSerial gen = do
    prop ("Data.Serialize encoding/decoding identity for " <> name) $
        forAll gen $ \x -> (S.decode . S.encode) x `shouldBe` Right x
  where
    name = show $ T.typeRep $ proxy gen
    proxy :: Gen a -> Proxy a
    proxy = const Proxy

-- | Generate Read/Show identity tests
testRead ::
       (Eq a, Read a, Show a, T.Typeable a) => Gen a -> Spec
testRead gen = do
    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 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 Data.Aeson identity tests for type that need the @Network@
testNetJson ::
       (Eq a, Show a, T.Typeable a)
    => (Network -> a -> A.Value)
    -> (Network -> a -> A.Encoding)
    -> (Network -> A.Value -> A.Parser a)
    -> Gen (Network, a)
    -> Spec
testNetJson j e p g = do
    prop ("Data.Aeson toJSON/fromJSON identity (with network) for " <> name) $
        forAll g $ \(net, x) -> dec net (encVal net x) `shouldBe` Just x
    prop ("Data.Aeson toEncoding/fromJSON identity (with network) for " <> name) $
        forAll g $ \(net, x) -> dec net (encEnc net x) `shouldBe` Just x
  where
    encVal net = A.encode . toMap . j net
    encEnc net = A.encodingToLazyByteString . toMapE . e net
    dec net = A.parseMaybe (p net) . fromMap <=< A.decode
    name = show $ T.typeRep $ proxy j
    proxy :: (Network -> a -> A.Value) -> Proxy a
    proxy = const Proxy

arbitraryNetData :: Arbitrary a => Gen (Network, a)
arbitraryNetData = do
    net <- arbitraryNetwork
    x <- arbitrary
    return (net, x)

genNetData :: Gen a -> Gen (Network, a)
genNetData gen = do
    net <- arbitraryNetwork
    x <- gen
    return (net, x)

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")