mirror of
https://github.com/unclechu/gRPC-haskell.git
synced 2024-11-26 21:19:43 +01:00
Fix metadata marshalling bug, add QuickCheck properties (#63)
* make metadatamap a newtype of multimap * update tests for new MetadataMap * remove multimap dependency, reproduce needed functionality instead * add repeated key to payload test * QuickCheck properties for C bindings, fix bug in metadata FFI wrappers * add quickcheck bound * fix merge
This commit is contained in:
parent
d6fb5df411
commit
b9b548d8eb
5 changed files with 89 additions and 25 deletions
|
@ -166,11 +166,12 @@ void metadata_free(grpc_metadata* m){
|
||||||
grpc_haskell_free("metadata_free", m);
|
grpc_haskell_free("metadata_free", m);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_metadata_key_val(char *key, char *val, grpc_metadata *arr, size_t i){
|
void set_metadata_key_val(char *key, char *val, size_t val_len,
|
||||||
|
grpc_metadata *arr, size_t i){
|
||||||
grpc_metadata *p = arr + i;
|
grpc_metadata *p = arr + i;
|
||||||
p->key = key;
|
p->key = key;
|
||||||
p->value = val;
|
p->value = val;
|
||||||
p->value_length = strlen(val);
|
p->value_length = val_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* get_metadata_key(grpc_metadata *arr, size_t i){
|
const char* get_metadata_key(grpc_metadata *arr, size_t i){
|
||||||
|
@ -183,6 +184,11 @@ const char* get_metadata_val(grpc_metadata *arr, size_t i){
|
||||||
return p->value;
|
return p->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t get_metadata_val_len(grpc_metadata *arr, size_t i){
|
||||||
|
grpc_metadata *p = arr + i;
|
||||||
|
return p->value_length;
|
||||||
|
}
|
||||||
|
|
||||||
grpc_op* op_array_create(size_t n){
|
grpc_op* op_array_create(size_t n){
|
||||||
grpc_op* ops = malloc(n*sizeof(grpc_op));
|
grpc_op* ops = malloc(n*sizeof(grpc_op));
|
||||||
memset(ops, 0, n*sizeof(grpc_op));
|
memset(ops, 0, n*sizeof(grpc_op));
|
||||||
|
|
|
@ -187,6 +187,7 @@ test-suite test
|
||||||
, clock >=0.6.0 && <0.8.0
|
, clock >=0.6.0 && <0.8.0
|
||||||
, turtle >= 1.2.0
|
, turtle >= 1.2.0
|
||||||
, text
|
, text
|
||||||
|
, QuickCheck ==2.8.*
|
||||||
other-modules:
|
other-modules:
|
||||||
LowLevelTests,
|
LowLevelTests,
|
||||||
LowLevelTests.Op,
|
LowLevelTests.Op,
|
||||||
|
|
|
@ -64,12 +64,15 @@ grpc_metadata* metadata_alloc(size_t n);
|
||||||
|
|
||||||
void metadata_free(grpc_metadata* m);
|
void metadata_free(grpc_metadata* m);
|
||||||
|
|
||||||
void set_metadata_key_val(char *key, char *val, grpc_metadata *arr, size_t i);
|
void set_metadata_key_val(char *key, char *val, size_t val_len,
|
||||||
|
grpc_metadata *arr, size_t i);
|
||||||
|
|
||||||
const char* get_metadata_key(grpc_metadata *arr, size_t i);
|
const char* get_metadata_key(grpc_metadata *arr, size_t i);
|
||||||
|
|
||||||
const char* get_metadata_val(grpc_metadata *arr, size_t i);
|
const char* get_metadata_val(grpc_metadata *arr, size_t i);
|
||||||
|
|
||||||
|
size_t get_metadata_val_len(grpc_metadata *arr, size_t i);
|
||||||
|
|
||||||
grpc_op* op_array_create(size_t n);
|
grpc_op* op_array_create(size_t n);
|
||||||
|
|
||||||
void op_array_destroy(grpc_op* op_array, size_t n);
|
void op_array_destroy(grpc_op* op_array, size_t n);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{-# LANGUAGE StandaloneDeriving #-}
|
{-# LANGUAGE StandaloneDeriving #-}
|
||||||
{-# LANGUAGE TupleSections #-}
|
{-# LANGUAGE TupleSections #-}
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
module Network.GRPC.Unsafe.Metadata where
|
module Network.GRPC.Unsafe.Metadata where
|
||||||
|
|
||||||
|
@ -8,6 +8,9 @@ import Control.Exception
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Data.ByteString (ByteString, useAsCString, packCString)
|
import Data.ByteString (ByteString, useAsCString, packCString)
|
||||||
import Data.Function (on)
|
import Data.Function (on)
|
||||||
|
import Data.List (sortBy, groupBy, (\\))
|
||||||
|
import Data.ByteString (ByteString, useAsCString,
|
||||||
|
useAsCStringLen, packCString, packCStringLen)
|
||||||
import Data.List (sortBy, groupBy)
|
import Data.List (sortBy, groupBy)
|
||||||
import qualified Data.SortedList as SL
|
import qualified Data.SortedList as SL
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
|
@ -29,7 +32,6 @@ import GHC.Exts
|
||||||
newtype MetadataMap = MetadataMap {unMap :: M.Map ByteString (SL.SortedList ByteString)}
|
newtype MetadataMap = MetadataMap {unMap :: M.Map ByteString (SL.SortedList ByteString)}
|
||||||
deriving Eq
|
deriving Eq
|
||||||
|
|
||||||
|
|
||||||
instance Show MetadataMap where
|
instance Show MetadataMap where
|
||||||
show m = "fromList " ++ show (M.toList (unMap m))
|
show m = "fromList " ++ show (M.toList (unMap m))
|
||||||
|
|
||||||
|
@ -98,17 +100,27 @@ instance Storable MetadataArray where
|
||||||
-- | Sets a metadata key/value pair at the given index in the
|
-- | Sets a metadata key/value pair at the given index in the
|
||||||
-- 'MetadataKeyValPtr'. No error checking is performed to ensure the index is
|
-- 'MetadataKeyValPtr'. No error checking is performed to ensure the index is
|
||||||
-- in bounds!
|
-- in bounds!
|
||||||
{#fun unsafe set_metadata_key_val as setMetadataKeyVal
|
{#fun unsafe set_metadata_key_val as setMetadataKeyVal'
|
||||||
{useAsCString* `ByteString', useAsCString* `ByteString',
|
{useAsCString* `ByteString', `CString', `Int',
|
||||||
`MetadataKeyValPtr', `Int'} -> `()'#}
|
`MetadataKeyValPtr', `Int'} -> `()'#}
|
||||||
|
|
||||||
|
setMetadataKeyVal :: ByteString
|
||||||
|
-> ByteString
|
||||||
|
-> MetadataKeyValPtr
|
||||||
|
-> Int
|
||||||
|
-> IO ()
|
||||||
|
setMetadataKeyVal k v m i =
|
||||||
|
useAsCStringLen v $ \(vStr, vLen) -> setMetadataKeyVal' k vStr vLen m i
|
||||||
|
|
||||||
{#fun unsafe get_metadata_key as getMetadataKey'
|
{#fun unsafe get_metadata_key as getMetadataKey'
|
||||||
{`MetadataKeyValPtr', `Int'} -> `CString'#}
|
{`MetadataKeyValPtr', `Int'} -> `CString'#}
|
||||||
|
|
||||||
{#fun unsafe get_metadata_val as getMetadataVal'
|
{#fun unsafe get_metadata_val as getMetadataVal'
|
||||||
{`MetadataKeyValPtr', `Int'} -> `CString'#}
|
{`MetadataKeyValPtr', `Int'} -> `CString'#}
|
||||||
|
|
||||||
--TODO: The test suggests this is leaking.
|
{#fun unsafe get_metadata_val_len as getMetadataValLen
|
||||||
|
{`MetadataKeyValPtr', `Int'} -> `Int'#}
|
||||||
|
|
||||||
withMetadataArrayPtr :: (Ptr MetadataArray -> IO a) -> IO a
|
withMetadataArrayPtr :: (Ptr MetadataArray -> IO a) -> IO a
|
||||||
withMetadataArrayPtr = bracket metadataArrayCreate metadataArrayDestroy
|
withMetadataArrayPtr = bracket metadataArrayCreate metadataArrayDestroy
|
||||||
|
|
||||||
|
@ -119,7 +131,9 @@ getMetadataKey :: MetadataKeyValPtr -> Int -> IO ByteString
|
||||||
getMetadataKey m = getMetadataKey' m >=> packCString
|
getMetadataKey m = getMetadataKey' m >=> packCString
|
||||||
|
|
||||||
getMetadataVal :: MetadataKeyValPtr -> Int -> IO ByteString
|
getMetadataVal :: MetadataKeyValPtr -> Int -> IO ByteString
|
||||||
getMetadataVal m = getMetadataVal' m >=> packCString
|
getMetadataVal m i = do vStr <- getMetadataVal' m i
|
||||||
|
vLen <- getMetadataValLen m i
|
||||||
|
packCStringLen (vStr, vLen)
|
||||||
|
|
||||||
createMetadata :: MetadataMap -> IO MetadataKeyValPtr
|
createMetadata :: MetadataMap -> IO MetadataKeyValPtr
|
||||||
createMetadata m = do
|
createMetadata m = do
|
||||||
|
|
|
@ -23,14 +23,14 @@ import Test.Tasty.HUnit as HU (testCase, (@?=),
|
||||||
assertBool)
|
assertBool)
|
||||||
import Test.Tasty.QuickCheck as QC
|
import Test.Tasty.QuickCheck as QC
|
||||||
import Test.Tasty.HUnit as HU (testCase, (@?=))
|
import Test.Tasty.HUnit as HU (testCase, (@?=))
|
||||||
|
import Test.QuickCheck.Gen as QC
|
||||||
|
import Test.QuickCheck.Property as QC
|
||||||
|
|
||||||
unsafeTests :: TestTree
|
unsafeTests :: TestTree
|
||||||
unsafeTests = testGroup "Unit tests for unsafe C bindings"
|
unsafeTests = testGroup "Unit tests for unsafe C bindings"
|
||||||
[ roundtripSlice "Hello, world!"
|
[ roundtripSliceUnit "\NULabc\NUL"
|
||||||
, roundtripSlice "\NULabc\NUL"
|
, roundtripSliceUnit largeByteString
|
||||||
, roundtripByteBuffer "Hwaet! We gardena in geardagum..."
|
, roundtripByteBufferUnit largeByteString
|
||||||
, roundtripSlice largeByteString
|
|
||||||
, roundtripByteBuffer largeByteString
|
|
||||||
, roundtripTimeSpec (TimeSpec 123 123)
|
, roundtripTimeSpec (TimeSpec 123 123)
|
||||||
, testMetadata
|
, testMetadata
|
||||||
, testNow
|
, testNow
|
||||||
|
@ -42,13 +42,34 @@ unsafeTests = testGroup "Unit tests for unsafe C bindings"
|
||||||
|
|
||||||
unsafeProperties :: TestTree
|
unsafeProperties :: TestTree
|
||||||
unsafeProperties = testGroup "QuickCheck properties for unsafe C bindings"
|
unsafeProperties = testGroup "QuickCheck properties for unsafe C bindings"
|
||||||
[ metadataIsList ]
|
[ roundtripSliceQC
|
||||||
|
, roundtripByteBufferQC
|
||||||
|
, roundtripMetadataQC
|
||||||
|
, metadataIsList
|
||||||
|
]
|
||||||
|
|
||||||
instance Arbitrary B.ByteString where
|
instance Arbitrary B.ByteString where
|
||||||
arbitrary = B.pack <$> arbitrary
|
arbitrary = B.pack <$> arbitrary
|
||||||
|
|
||||||
instance Arbitrary MetadataMap where
|
instance Arbitrary MetadataMap where
|
||||||
arbitrary = fromList <$> arbitrary
|
arbitrary = do
|
||||||
|
--keys are not allowed to contain \NUL, but values are.
|
||||||
|
ks <- arbitrary `suchThat` all (B.notElem 0)
|
||||||
|
let l = length ks
|
||||||
|
vs <- vector l
|
||||||
|
return $ fromList (zip ks vs)
|
||||||
|
|
||||||
|
roundtripMetadataKeyVals :: MetadataMap -> IO MetadataMap
|
||||||
|
roundtripMetadataKeyVals m = do
|
||||||
|
kvPtr <- createMetadata m
|
||||||
|
m' <- getAllMetadata kvPtr (length $ toList m)
|
||||||
|
metadataFree kvPtr
|
||||||
|
return m'
|
||||||
|
|
||||||
|
roundtripMetadataQC :: TestTree
|
||||||
|
roundtripMetadataQC = QC.testProperty "Metadata roundtrip" $
|
||||||
|
\m -> QC.ioProperty $ do m' <- roundtripMetadataKeyVals m
|
||||||
|
return $ m === m'
|
||||||
|
|
||||||
metadataIsList :: TestTree
|
metadataIsList :: TestTree
|
||||||
metadataIsList = QC.testProperty "Metadata IsList instance" $
|
metadataIsList = QC.testProperty "Metadata IsList instance" $
|
||||||
|
@ -57,26 +78,45 @@ metadataIsList = QC.testProperty "Metadata IsList instance" $
|
||||||
largeByteString :: B.ByteString
|
largeByteString :: B.ByteString
|
||||||
largeByteString = B.pack $ take (32*1024*1024) $ cycle [97..99]
|
largeByteString = B.pack $ take (32*1024*1024) $ cycle [97..99]
|
||||||
|
|
||||||
roundtripSlice :: B.ByteString -> TestTree
|
roundtripSlice :: B.ByteString -> IO B.ByteString
|
||||||
roundtripSlice bs = testCase "ByteString slice roundtrip" $ do
|
roundtripSlice bs = do
|
||||||
slice <- byteStringToSlice bs
|
slice <- byteStringToSlice bs
|
||||||
unslice <- sliceToByteString slice
|
unslice <- sliceToByteString slice
|
||||||
unslice HU.@?= bs
|
|
||||||
freeSlice slice
|
freeSlice slice
|
||||||
|
return unslice
|
||||||
|
|
||||||
roundtripByteBuffer :: B.ByteString -> TestTree
|
roundtripSliceQC :: TestTree
|
||||||
roundtripByteBuffer bs = testCase "ByteBuffer roundtrip" $ do
|
roundtripSliceQC = QC.testProperty "Slice roundtrip: QuickCheck" $
|
||||||
|
\bs -> QC.ioProperty $ do bs' <- roundtripSlice bs
|
||||||
|
return $ bs == bs'
|
||||||
|
|
||||||
|
roundtripSliceUnit :: B.ByteString -> TestTree
|
||||||
|
roundtripSliceUnit bs = testCase "ByteString slice roundtrip" $ do
|
||||||
|
unslice <- roundtripSlice bs
|
||||||
|
unslice HU.@?= bs
|
||||||
|
|
||||||
|
roundtripByteBuffer :: B.ByteString -> IO B.ByteString
|
||||||
|
roundtripByteBuffer bs = do
|
||||||
slice <- byteStringToSlice bs
|
slice <- byteStringToSlice bs
|
||||||
buffer <- grpcRawByteBufferCreate slice 1
|
buffer <- grpcRawByteBufferCreate slice 1
|
||||||
reader <- byteBufferReaderCreate buffer
|
reader <- byteBufferReaderCreate buffer
|
||||||
readSlice <- grpcByteBufferReaderReadall reader
|
readSlice <- grpcByteBufferReaderReadall reader
|
||||||
bs' <- sliceToByteString readSlice
|
bs' <- sliceToByteString readSlice
|
||||||
bs' HU.@?= bs
|
|
||||||
-- clean up
|
|
||||||
freeSlice slice
|
freeSlice slice
|
||||||
byteBufferReaderDestroy reader
|
byteBufferReaderDestroy reader
|
||||||
grpcByteBufferDestroy buffer
|
grpcByteBufferDestroy buffer
|
||||||
freeSlice readSlice
|
freeSlice readSlice
|
||||||
|
return bs'
|
||||||
|
|
||||||
|
roundtripByteBufferQC :: TestTree
|
||||||
|
roundtripByteBufferQC = QC.testProperty "ByteBuffer roundtrip: QuickCheck" $
|
||||||
|
\bs -> QC.ioProperty $ do bs' <- roundtripByteBuffer bs
|
||||||
|
return $ bs == bs'
|
||||||
|
|
||||||
|
roundtripByteBufferUnit :: B.ByteString -> TestTree
|
||||||
|
roundtripByteBufferUnit bs = testCase "ByteBuffer roundtrip" $ do
|
||||||
|
bs' <- roundtripByteBuffer bs
|
||||||
|
bs' HU.@?= bs
|
||||||
|
|
||||||
roundtripTimeSpec :: TimeSpec -> TestTree
|
roundtripTimeSpec :: TimeSpec -> TestTree
|
||||||
roundtripTimeSpec t = testCase "CTimeSpec roundtrip" $ do
|
roundtripTimeSpec t = testCase "CTimeSpec roundtrip" $ do
|
||||||
|
|
Loading…
Reference in a new issue