streamly/test/Streamly/Test/Data/Unbox.hs

348 lines
9.7 KiB
Haskell

{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TemplateHaskell #-}
-- This module has a lot of orphan instances as we are deriving it here. We can
-- ignore this warning.
{-# OPTIONS_GHC -Wno-orphans #-}
-- |
-- Module : Streamly.Test.Data.Unbox
-- Copyright : (c) 2022 Composewell technologies
-- License : BSD-3-Clause
-- Maintainer : streamly@composewell.com
-- Stability : experimental
-- Portability : GHC
module Streamly.Test.Data.Unbox (main) where
--------------------------------------------------------------------------------
-- Imports
--------------------------------------------------------------------------------
#ifdef USE_SERIALIZE
import Data.Foldable (forM_)
import Data.Word (Word8)
import qualified Streamly.Internal.Data.Array as Array
#endif
import Data.Complex (Complex ((:+)))
import Data.Functor.Const (Const (..))
import Data.Functor.Identity (Identity (..))
import Data.Proxy (Proxy(..))
import GHC.Generics (Generic, Rep(..))
import GHC.Real (Ratio(..))
import Streamly.Internal.Data.MutByteArray
import qualified Streamly.Internal.Data.MutByteArray as MBA
import Test.Hspec as H
--------------------------------------------------------------------------------
-- Types
--------------------------------------------------------------------------------
#ifdef USE_SERIALIZE
#define MODULE_NAME "Data.Serialize.Deriving.TH"
#define DERIVE_UNBOX(typ) $(deriveSerialize [d|instance Serialize typ|])
#define PEEK(i, arr, sz) (deserializeAt i arr sz)
#define POKE(i, arr, val) (serializeAt i arr val)
#define TYPE_CLASS Serialize
#else
#define PEEK(i, arr, sz) peekAtWithNextOff i arr
#define POKE(i, arr, val) pokeAtWithNextOff i arr val
#define TYPE_CLASS Unbox
#ifdef USE_TH
#define MODULE_NAME "Data.Unbox.Deriving.TH"
#define DERIVE_UNBOX(typ) $(deriveUnbox [d|instance Unbox typ|])
#else
#define MODULE_NAME "Data.Unbox.Deriving.Generic"
#define DERIVE_UNBOX(typ) deriving instance Unbox (typ)
#endif
#endif
--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
#ifndef USE_SERIALIZE
peekAtWithNextOff ::
forall a. Unbox a
=> Int
-> MutByteArray
-> IO (Int, a)
peekAtWithNextOff i arr = do
val <- peekAt i arr
pure (i + sizeOf (Proxy :: Proxy a), val)
pokeAtWithNextOff ::
forall a. Unbox a
=> Int
-> MutByteArray
-> a
-> IO Int
pokeAtWithNextOff i arr val = do
pokeAt i arr val
pure $ i + sizeOf (Proxy :: Proxy a)
#endif
--------------------------------------------------------------------------------
-- Types
--------------------------------------------------------------------------------
-- Unit instance uses a hack, so test all cases
data Unit =
Unit
deriving (Show, Generic, Eq)
DERIVE_UNBOX(Unit)
data UnarySum
= Sum1
| Sum2
deriving (Show, Generic, Eq)
DERIVE_UNBOX(UnarySum)
data UnarySum2
= UnitSum1 Unit
| UnitSum2 Unit
deriving (Generic, Eq, Show)
DERIVE_UNBOX(UnarySum2)
data Unit1 =
Unit1 Unit
deriving (Generic, Eq, Show)
DERIVE_UNBOX(Unit1)
data Unit2 =
Unit2 Unit Unit
deriving (Generic, Eq, Show)
DERIVE_UNBOX(Unit2)
data Unit3 =
Unit3 Int Unit Int
deriving (Generic, Eq, Show)
DERIVE_UNBOX(Unit3)
data Unit4 =
Unit4 Int Unit1 Int
deriving (Generic, Eq, Show)
DERIVE_UNBOX(Unit4)
{-# ANN Single "HLint: ignore" #-}
data Single =
Single Int
deriving (Show, Generic, Eq)
DERIVE_UNBOX(Single)
data Product2 =
Product2 Int Char
deriving (Show, Generic, Eq)
DERIVE_UNBOX(Product2)
data SumOfProducts
= SOP0
| SOP1 Int
| SOP2 Int Char
| SOP3 Int Int Int
deriving (Show, Generic, Eq)
DERIVE_UNBOX(SumOfProducts)
data NestedSOP
= NSOP0 SumOfProducts
| NSOP1 SumOfProducts
deriving (Show, Generic, Eq)
DERIVE_UNBOX(NestedSOP)
--------------------------------------------------------------------------------
-- Standalone derivations
--------------------------------------------------------------------------------
-- Ratio does not have a Generic instance by default
deriving instance Generic (Ratio Int)
#if defined(USE_SERIALIZE)
$(deriveSerialize
[d|instance Serialize a => Serialize (Complex a)|])
$(deriveSerialize
[d|instance Serialize a => Serialize (Ratio a)|])
$(deriveSerialize
[d|instance Serialize a => Serialize (Const a b)|])
$(deriveSerialize
[d|instance Serialize a => Serialize (Identity a)|])
#endif
--------------------------------------------------------------------------------
-- Test helpers
--------------------------------------------------------------------------------
#ifdef USE_SERIALIZE
variableSizeOf ::
forall a. Serialize a
=> a
-> Int
variableSizeOf = addSizeTo 0
#endif
testSerialization ::
forall a. (Eq a, Show a, TYPE_CLASS a)
=> a
-> IO ()
testSerialization val = do
let len =
#ifdef USE_SERIALIZE
(variableSizeOf val)
#else
(sizeOf (Proxy :: Proxy a))
#endif
arr <- MBA.new len
nextOff <- POKE(0, arr, val)
#ifdef USE_SERIALIZE
arr2 <- MBA.new len
-- Re-initialize the array with random value
forM_ [0..(len - 1)] $ \i -> POKE(i, arr2, (8 :: Word8))
_ <- POKE(0, arr2, val)
let slice1 = Array.Array arr 0 len :: Array.Array Word8
slice2 = Array.Array arr2 0 len :: Array.Array Word8
-- The serialized representation should be the same
slice1 `shouldBe` slice2
-- The serialized representation is not the same for "Unbox" as the Array
-- might not be fully utilized in case of "Unbox". This is because different
-- constructors might have different lengths.
#endif
(nextOff1, val1) <- PEEK(0, arr, len)
val1 `shouldBe` val
nextOff1 `shouldBe` len
nextOff `shouldBe` len
testGenericConsistency ::
forall a.
( Eq a
, Show a
#ifdef USE_SERIALIZE
, Serialize a
#endif
, Unbox a
, Generic a
, SizeOfRep (Rep a)
, PeekRep (Rep a)
, PokeRep (Rep a)
)
=> a
-> IO ()
testGenericConsistency val = do
-- Test the generic sizeOf
let len =
#ifdef USE_SERIALIZE
variableSizeOf val
#else
sizeOf (Proxy :: Proxy a)
#endif
len `shouldBe` genericSizeOf (Proxy :: Proxy a)
-- Test the serialization and deserialization
arr <- MBA.new (sizeOf (Proxy :: Proxy a))
nextOff <- POKE(0, arr, val)
genericPeekByteIndex arr 0 `shouldReturn` val
genericPokeByteIndex arr 0 val
(nextOff1, val1) <- PEEK(0, arr, len)
val1 `shouldBe` val
nextOff1 `shouldBe` len
nextOff `shouldBe` len
#ifndef USE_SERIALIZE
-- Size is also implicitly tested while serializing and deserializing.
checkSizeOf :: forall a. Unbox a => Proxy a -> Int -> IO ()
checkSizeOf _ sz = sizeOf (Proxy :: Proxy a) `shouldBe` sz
#endif
--------------------------------------------------------------------------------
-- CPP helpers
--------------------------------------------------------------------------------
#define CHECK_SIZE(type, expectation) \
it "checkSizeOf type" $ checkSizeOf (Proxy :: Proxy type) expectation
--------------------------------------------------------------------------------
-- Tests
--------------------------------------------------------------------------------
testCases :: Spec
testCases = do
it "Unit" $ testSerialization Unit
it "Unit1" $ testSerialization (Unit1 Unit)
it "Unit2" $ testSerialization (Unit2 Unit Unit)
it "Unit3" $ testSerialization (Unit3 1234 Unit 4567)
it "Unit4" $ testSerialization (Unit4 1234 (Unit1 Unit) 4567)
it "UnarySum Sum1" $ testSerialization Sum1
it "UnarySum Sum2" $ testSerialization Sum2
it "UnarySum2 UnitSum1" $ testSerialization (UnitSum1 Unit)
it "UnarySum2 UnitSum2" $ testSerialization (UnitSum2 Unit)
it "Single" $ testSerialization (Single 2)
it "Product2" $ testSerialization (Product2 2 'b')
it "SumOfProducts SOP0" $ testSerialization SOP0
it "SumOfProducts SOP1" $ testSerialization (SOP1 1)
it "SumOfProducts SOP2" $ testSerialization (SOP2 1 'a')
it "SumOfProducts SOP3" $ testSerialization (SOP3 1 2 3)
#ifndef USE_SERIALIZE
CHECK_SIZE(Unit, 1)
CHECK_SIZE(Unit1, 1)
CHECK_SIZE(Unit2, 2)
CHECK_SIZE(Unit3, 17)
CHECK_SIZE(Unit4, 17)
CHECK_SIZE(UnarySum, 1)
CHECK_SIZE(UnarySum2, 2)
CHECK_SIZE(Single, 8)
CHECK_SIZE(Product2, 12)
CHECK_SIZE(SumOfProducts, 25)
CHECK_SIZE(NestedSOP, 26)
#endif
it "Bool" $ testSerialization True
it "Complex Int" $ testSerialization (5 :+ 3 :: Complex Int)
it "Ratio Int" $ testSerialization (5 :% 3 :: Ratio Int)
it "Const Float Int" $ testSerialization (Const 333.5678 :: Const Float Int)
it "Identity Int" $ testSerialization (Identity 56760 :: Identity Int)
it "GenericConsistency Bool" $ testGenericConsistency True
it "GenericConsistency (Complex Int)"
$ testGenericConsistency (5 :+ 3 :: Complex Int)
it "GenericConsistency (Ratio Int)"
$ testGenericConsistency (5 :% 3 :: Ratio Int)
it "GenericConsistency (Const Float Int)"
$ testGenericConsistency (Const 333.5678 :: Const Float Int)
it "GenericConsistency (Identity Int)"
$ testGenericConsistency (Identity 56760 :: Identity Int)
-- Fingerprint does not work for GHC 8.6.5
-- it "Fingerprint" $ testSerialization (Fingerprint 123456 876588)
-- it "GenericConsistency Fingerprint"
-- $ testGenericConsistency (Fingerprint 123456 876588)
--------------------------------------------------------------------------------
-- Main function
--------------------------------------------------------------------------------
moduleName :: String
moduleName = MODULE_NAME
main :: IO ()
main = hspec $ H.parallel $ describe moduleName testCases