Fix payload test bugs (#8)

* fix memory mgmt bug in status details strings

* remove flags

* allow server to specify status details
This commit is contained in:
Connor Clark 2016-05-25 13:11:30 -07:00 committed by Joel Stanley
parent 16208697fa
commit 8a0eef8ab7
12 changed files with 378 additions and 59 deletions

View file

@ -131,6 +131,10 @@ grpc_metadata_array** metadata_array_create(){
grpc_metadata_array **retval = malloc(sizeof(grpc_metadata_array*)); grpc_metadata_array **retval = malloc(sizeof(grpc_metadata_array*));
*retval = malloc(sizeof(grpc_metadata_array)); *retval = malloc(sizeof(grpc_metadata_array));
grpc_metadata_array_init(*retval); grpc_metadata_array_init(*retval);
#ifdef GRPC_HASKELL_DEBUG
printf("C wrapper: metadata_array_create debug: %p %p %p\n", retval, *retval,
(*retval)->metadata);
#endif
return retval; return retval;
} }
@ -299,7 +303,11 @@ void op_send_status_server(grpc_op *op_array, size_t i,
} }
grpc_status_code* create_status_code_ptr(){ grpc_status_code* create_status_code_ptr(){
return malloc(sizeof(grpc_status_code)); grpc_status_code* retval = malloc(sizeof(grpc_status_code));
#ifdef GRPC_HASKELL_DEBUG
printf("C wrapper: create_status_code_ptr debug: %p\n", retval);
#endif
return retval;
} }
grpc_status_code deref_status_code_ptr(grpc_status_code* p){ grpc_status_code deref_status_code_ptr(grpc_status_code* p){

View file

@ -47,6 +47,7 @@ GRPC
, runOps , runOps
, Op(..) , Op(..)
, OpRecvResult(..) , OpRecvResult(..)
, StatusDetails(..)
) where ) where

View file

@ -114,9 +114,10 @@ withClientCall client method host timeout f = do
data NormalRequestResult = NormalRequestResult data NormalRequestResult = NormalRequestResult
ByteString ByteString
MetadataMap --init metadata (Maybe MetadataMap) --init metadata
MetadataMap --trailing metadata MetadataMap --trailing metadata
C.StatusCode C.StatusCode
StatusDetails
deriving (Show, Eq) deriving (Show, Eq)
-- | Function for assembling call result when the 'MethodType' is 'Normal'. -- | Function for assembling call result when the 'MethodType' is 'Normal'.
@ -128,8 +129,14 @@ compileNormalRequestResults
-- core use cases easy. -- core use cases easy.
[OpRecvInitialMetadataResult m, [OpRecvInitialMetadataResult m,
OpRecvMessageResult body, OpRecvMessageResult body,
OpRecvStatusOnClientResult m2 status] OpRecvStatusOnClientResult m2 status details]
= NormalRequestResult body m m2 status = NormalRequestResult body (Just m) m2 status (StatusDetails details)
-- TODO: it seems registered request responses on the server
-- don't send initial metadata. Hence the 'Maybe'. Investigate.
compileNormalRequestResults
[OpRecvMessageResult body,
OpRecvStatusOnClientResult m2 status details]
= NormalRequestResult body Nothing m2 status (StatusDetails details)
compileNormalRequestResults _ = compileNormalRequestResults _ =
--TODO: impossible case should be enforced by more precise types. --TODO: impossible case should be enforced by more precise types.
error "non-normal request input to compileNormalRequestResults." error "non-normal request input to compileNormalRequestResults."

View file

@ -30,6 +30,7 @@ import Control.Concurrent.STM.TVar (TVar, newTVarIO, modifyTVar',
readTVar, writeTVar) readTVar, writeTVar)
import Control.Exception (bracket) import Control.Exception (bracket)
import Data.IORef (IORef, newIORef, atomicModifyIORef') import Data.IORef (IORef, newIORef, atomicModifyIORef')
import Data.List (intersperse)
import Foreign.Marshal.Alloc (malloc, free) import Foreign.Marshal.Alloc (malloc, free)
import Foreign.Ptr (nullPtr, plusPtr) import Foreign.Ptr (nullPtr, plusPtr)
import Foreign.Storable (peek) import Foreign.Storable (peek)
@ -188,7 +189,8 @@ startBatch :: CompletionQueue -> C.Call -> C.OpArray -> Int -> C.Tag
-> IO (Either GRPCIOError ()) -> IO (Either GRPCIOError ())
startBatch cq@CompletionQueue{..} call opArray opArraySize tag = startBatch cq@CompletionQueue{..} call opArray opArraySize tag =
withPermission Push cq $ fmap throwIfCallError $ do withPermission Push cq $ fmap throwIfCallError $ do
grpcDebug "startBatch: calling grpc_call_start_batch." grpcDebug $ "startBatch: calling grpc_call_start_batch with pointers: "
++ show call ++ " " ++ show opArray
res <- C.grpcCallStartBatch call opArray opArraySize tag C.reserved res <- C.grpcCallStartBatch call opArray opArraySize tag C.reserved
grpcDebug "startBatch: grpc_call_start_batch call returned." grpcDebug "startBatch: grpc_call_start_batch call returned."
return res return res
@ -230,6 +232,10 @@ channelCreateRegisteredCall :: C.Channel -> C.Call -> C.PropagationMask
channelCreateRegisteredCall channelCreateRegisteredCall
chan parent mask cq@CompletionQueue{..} handle deadline = chan parent mask cq@CompletionQueue{..} handle deadline =
withPermission Push cq $ do withPermission Push cq $ do
grpcDebug $ "channelCreateRegisteredCall: call with "
++ concat (intersperse " " [show chan, show parent, show mask,
show unsafeCQ, show handle,
show deadline])
call <- C.grpcChannelCreateRegisteredCall chan parent mask unsafeCQ call <- C.grpcChannelCreateRegisteredCall chan parent mask unsafeCQ
handle deadline C.reserved handle deadline C.reserved
return $ Right $ ClientCall call return $ Right $ ClientCall call

View file

@ -1,4 +1,5 @@
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Network.GRPC.LowLevel.Op where module Network.GRPC.LowLevel.Op where
@ -6,10 +7,12 @@ import Control.Exception
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import Data.Maybe (catMaybes) import Data.Maybe (catMaybes)
import Data.String (IsString)
import Foreign.C.String (CString)
import Foreign.C.Types (CInt) import Foreign.C.Types (CInt)
import Foreign.Marshal.Alloc (malloc, free) import Foreign.Marshal.Alloc (malloc, mallocBytes, free)
import Foreign.Ptr (Ptr) import Foreign.Ptr (Ptr, nullPtr)
import Foreign.Storable (peek) import Foreign.Storable (peek, poke)
import qualified Network.GRPC.Unsafe as C import qualified Network.GRPC.Unsafe as C
import qualified Network.GRPC.Unsafe.Metadata as C import qualified Network.GRPC.Unsafe.Metadata as C
import qualified Network.GRPC.Unsafe.ByteBuffer as C import qualified Network.GRPC.Unsafe.ByteBuffer as C
@ -21,13 +24,15 @@ import Network.GRPC.LowLevel.Call
type MetadataMap = M.Map B.ByteString B.ByteString type MetadataMap = M.Map B.ByteString B.ByteString
newtype StatusDetails = StatusDetails B.ByteString deriving (Show, Eq, IsString)
-- | Sum describing all possible send and receive operations that can be batched -- | Sum describing all possible send and receive operations that can be batched
-- and executed by gRPC. Usually these are processed in a handful of -- and executed by gRPC. Usually these are processed in a handful of
-- combinations depending on the 'MethodType' of the call being run. -- combinations depending on the 'MethodType' of the call being run.
data Op = OpSendInitialMetadata MetadataMap data Op = OpSendInitialMetadata MetadataMap
| OpSendMessage B.ByteString | OpSendMessage B.ByteString
| OpSendCloseFromClient | OpSendCloseFromClient
| OpSendStatusFromServer MetadataMap C.StatusCode --TODO: Issue #6 | OpSendStatusFromServer MetadataMap C.StatusCode StatusDetails
| OpRecvInitialMetadata | OpRecvInitialMetadata
| OpRecvMessage | OpRecvMessage
| OpRecvStatusOnClient | OpRecvStatusOnClient
@ -42,10 +47,19 @@ data OpContext =
| OpSendMessageContext C.ByteBuffer | OpSendMessageContext C.ByteBuffer
| OpSendCloseFromClientContext | OpSendCloseFromClientContext
| OpSendStatusFromServerContext C.MetadataKeyValPtr Int C.StatusCode | OpSendStatusFromServerContext C.MetadataKeyValPtr Int C.StatusCode
B.ByteString
| OpRecvInitialMetadataContext (Ptr C.MetadataArray) | OpRecvInitialMetadataContext (Ptr C.MetadataArray)
| OpRecvMessageContext (Ptr C.ByteBuffer) | OpRecvMessageContext (Ptr C.ByteBuffer)
| OpRecvStatusOnClientContext (Ptr C.MetadataArray) (Ptr C.StatusCode) | OpRecvStatusOnClientContext (Ptr C.MetadataArray) (Ptr C.StatusCode)
(Ptr CString)
| OpRecvCloseOnServerContext (Ptr CInt) | OpRecvCloseOnServerContext (Ptr CInt)
deriving Show
-- | Length we pass to gRPC for receiving status details
-- when processing 'OpRecvStatusOnClient'. It appears that gRPC actually ignores
-- this length and reallocates a longer string if necessary.
defaultStatusStringLen :: Int
defaultStatusStringLen = 128
-- | Allocates and initializes the 'Opcontext' corresponding to the given 'Op'. -- | Allocates and initializes the 'Opcontext' corresponding to the given 'Op'.
createOpContext :: Op -> IO OpContext createOpContext :: Op -> IO OpContext
@ -56,19 +70,23 @@ createOpContext (OpSendInitialMetadata m) =
createOpContext (OpSendMessage bs) = createOpContext (OpSendMessage bs) =
fmap OpSendMessageContext (C.createByteBuffer bs) fmap OpSendMessageContext (C.createByteBuffer bs)
createOpContext (OpSendCloseFromClient) = return OpSendCloseFromClientContext createOpContext (OpSendCloseFromClient) = return OpSendCloseFromClientContext
createOpContext (OpSendStatusFromServer m code) = createOpContext (OpSendStatusFromServer m code (StatusDetails str)) =
OpSendStatusFromServerContext OpSendStatusFromServerContext
<$> C.createMetadata m <$> C.createMetadata m
<*> return (M.size m) <*> return (M.size m)
<*> return code <*> return code
<*> return str
createOpContext OpRecvInitialMetadata = createOpContext OpRecvInitialMetadata =
fmap OpRecvInitialMetadataContext C.metadataArrayCreate fmap OpRecvInitialMetadataContext C.metadataArrayCreate
createOpContext OpRecvMessage = createOpContext OpRecvMessage =
fmap OpRecvMessageContext C.createReceivingByteBuffer fmap OpRecvMessageContext C.createReceivingByteBuffer
createOpContext OpRecvStatusOnClient = createOpContext OpRecvStatusOnClient = do
OpRecvStatusOnClientContext pmetadata <- C.metadataArrayCreate
<$> C.metadataArrayCreate pstatus <- C.createStatusCodePtr
<*> C.createStatusCodePtr pstr <- malloc
cstring <- mallocBytes defaultStatusStringLen
poke pstr cstring
return $ OpRecvStatusOnClientContext pmetadata pstatus pstr
createOpContext OpRecvCloseOnServer = createOpContext OpRecvCloseOnServer =
fmap OpRecvCloseOnServerContext $ malloc fmap OpRecvCloseOnServerContext $ malloc
@ -81,15 +99,15 @@ setOpArray arr i (OpSendMessageContext bb) =
C.opSendMessage arr i bb C.opSendMessage arr i bb
setOpArray arr i OpSendCloseFromClientContext = setOpArray arr i OpSendCloseFromClientContext =
C.opSendCloseClient arr i C.opSendCloseClient arr i
setOpArray arr i (OpSendStatusFromServerContext kvs l code) = setOpArray arr i (OpSendStatusFromServerContext kvs l code details) =
C.opSendStatusServer arr i l kvs code "" --TODO: Issue #6 B.useAsCString details $ \cstr ->
C.opSendStatusServer arr i l kvs code cstr
setOpArray arr i (OpRecvInitialMetadataContext pmetadata) = setOpArray arr i (OpRecvInitialMetadataContext pmetadata) =
C.opRecvInitialMetadata arr i pmetadata C.opRecvInitialMetadata arr i pmetadata
setOpArray arr i (OpRecvMessageContext pbb) = setOpArray arr i (OpRecvMessageContext pbb) =
C.opRecvMessage arr i pbb C.opRecvMessage arr i pbb
setOpArray arr i (OpRecvStatusOnClientContext pmetadata pstatus) = do setOpArray arr i (OpRecvStatusOnClientContext pmetadata pstatus pstr) = do
pCString <- malloc --TODO: Issue #6 C.opRecvStatusClient arr i pmetadata pstatus pstr defaultStatusStringLen
C.opRecvStatusClient arr i pmetadata pstatus pCString 0
setOpArray arr i (OpRecvCloseOnServerContext pcancelled) = do setOpArray arr i (OpRecvCloseOnServerContext pcancelled) = do
C.opRecvCloseServer arr i pcancelled C.opRecvCloseServer arr i pcancelled
@ -98,15 +116,18 @@ freeOpContext :: OpContext -> IO ()
freeOpContext (OpSendInitialMetadataContext m _) = C.metadataFree m freeOpContext (OpSendInitialMetadataContext m _) = C.metadataFree m
freeOpContext (OpSendMessageContext bb) = C.grpcByteBufferDestroy bb freeOpContext (OpSendMessageContext bb) = C.grpcByteBufferDestroy bb
freeOpContext OpSendCloseFromClientContext = return () freeOpContext OpSendCloseFromClientContext = return ()
freeOpContext (OpSendStatusFromServerContext metadata _ _) = freeOpContext (OpSendStatusFromServerContext metadata _ _ _) =
C.metadataFree metadata C.metadataFree metadata
freeOpContext (OpRecvInitialMetadataContext metadata) = freeOpContext (OpRecvInitialMetadataContext metadata) =
C.metadataArrayDestroy metadata C.metadataArrayDestroy metadata
freeOpContext (OpRecvMessageContext pbb) = freeOpContext (OpRecvMessageContext pbb) =
C.destroyReceivingByteBuffer pbb C.destroyReceivingByteBuffer pbb
freeOpContext (OpRecvStatusOnClientContext metadata pcode) = freeOpContext (OpRecvStatusOnClientContext metadata pcode pstr) = do
C.metadataArrayDestroy metadata C.metadataArrayDestroy metadata
>> C.destroyStatusCodePtr pcode C.destroyStatusCodePtr pcode
str <- peek pstr
free str
free pstr
freeOpContext (OpRecvCloseOnServerContext pcancelled) = freeOpContext (OpRecvCloseOnServerContext pcancelled) =
grpcDebug ("freeOpContext: freeing pcancelled: " ++ show pcancelled) grpcDebug ("freeOpContext: freeing pcancelled: " ++ show pcancelled)
>> free pcancelled >> free pcancelled
@ -125,7 +146,7 @@ withOpArray n = bracket (C.opArrayCreate n)
data OpRecvResult = data OpRecvResult =
OpRecvInitialMetadataResult MetadataMap OpRecvInitialMetadataResult MetadataMap
| OpRecvMessageResult B.ByteString | OpRecvMessageResult B.ByteString
| OpRecvStatusOnClientResult MetadataMap C.StatusCode | OpRecvStatusOnClientResult MetadataMap C.StatusCode B.ByteString
| OpRecvCloseOnServerResult Bool -- ^ True if call was cancelled. | OpRecvCloseOnServerResult Bool -- ^ True if call was cancelled.
deriving (Eq, Show) deriving (Eq, Show)
@ -145,12 +166,14 @@ resultFromOpContext (OpRecvMessageContext pbb) = do
bs <- C.copyByteBufferToByteString bb bs <- C.copyByteBufferToByteString bb
grpcDebug "resultFromOpContext: bb copied." grpcDebug "resultFromOpContext: bb copied."
return $ Just $ OpRecvMessageResult bs return $ Just $ OpRecvMessageResult bs
resultFromOpContext (OpRecvStatusOnClientContext pmetadata pcode) = do resultFromOpContext (OpRecvStatusOnClientContext pmetadata pcode pstr) = do
grpcDebug "resultFromOpContext: OpRecvStatusOnClientContext" grpcDebug "resultFromOpContext: OpRecvStatusOnClientContext"
metadata <- peek pmetadata metadata <- peek pmetadata
metadataMap <- C.getAllMetadataArray metadata metadataMap <- C.getAllMetadataArray metadata
code <- C.derefStatusCodePtr pcode code <- C.derefStatusCodePtr pcode
return $ Just $ OpRecvStatusOnClientResult metadataMap code cstr <- peek pstr
statusInfo <- B.packCString cstr
return $ Just $ OpRecvStatusOnClientResult metadataMap code statusInfo
resultFromOpContext (OpRecvCloseOnServerContext pcancelled) = do resultFromOpContext (OpRecvCloseOnServerContext pcancelled) = do
grpcDebug "resultFromOpContext: OpRecvCloseOnServerContext" grpcDebug "resultFromOpContext: OpRecvCloseOnServerContext"
cancelled <- fmap (\x -> if x > 0 then True else False) cancelled <- fmap (\x -> if x > 0 then True else False)
@ -185,7 +208,7 @@ runOps call cq ops timeLimit =
withOpArray l $ \opArray -> do withOpArray l $ \opArray -> do
grpcDebug "runOps: created op array." grpcDebug "runOps: created op array."
withOpContexts ops $ \contexts -> do withOpContexts ops $ \contexts -> do
grpcDebug "runOps: allocated op contexts." grpcDebug $ "runOps: allocated op contexts: " ++ show contexts
sequence_ $ zipWith (setOpArray opArray) [0..l-1] contexts sequence_ $ zipWith (setOpArray opArray) [0..l-1] contexts
tag <- newTag cq tag <- newTag cq
callError <- startBatch cq (internalCall call) opArray l tag callError <- startBatch cq (internalCall call) opArray l tag

View file

@ -2,6 +2,7 @@
module Network.GRPC.LowLevel.Server where module Network.GRPC.LowLevel.Server where
import Control.Concurrent (threadDelay)
import Control.Exception (bracket, finally) import Control.Exception (bracket, finally)
import Control.Monad import Control.Monad
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
@ -159,11 +160,12 @@ serverOpsGetNormalCall initMetadata =
serverOpsSendNormalResponse :: ByteString serverOpsSendNormalResponse :: ByteString
-> MetadataMap -> MetadataMap
-> C.StatusCode -> C.StatusCode
-> StatusDetails
-> [Op] -> [Op]
serverOpsSendNormalResponse body metadata code = serverOpsSendNormalResponse body metadata code details =
[OpRecvCloseOnServer, [OpRecvCloseOnServer,
OpSendMessage body, OpSendMessage body,
OpSendStatusFromServer metadata code] OpSendStatusFromServer metadata code details]
serverOpsSendNormalRegisteredResponse :: ByteString serverOpsSendNormalRegisteredResponse :: ByteString
-> MetadataMap -> MetadataMap
@ -171,12 +173,14 @@ serverOpsSendNormalRegisteredResponse :: ByteString
-> MetadataMap -> MetadataMap
-- ^ trailing metadata -- ^ trailing metadata
-> C.StatusCode -> C.StatusCode
-> StatusDetails
-> [Op] -> [Op]
serverOpsSendNormalRegisteredResponse body initMetadata trailingMeta code = serverOpsSendNormalRegisteredResponse
body initMetadata trailingMeta code details =
[OpSendInitialMetadata initMetadata, [OpSendInitialMetadata initMetadata,
OpRecvCloseOnServer, OpRecvCloseOnServer,
OpSendMessage body, OpSendMessage body,
OpSendStatusFromServer trailingMeta code] OpSendStatusFromServer trailingMeta code details]
-- TODO: we will want to replace this with some more general concept that also -- TODO: we will want to replace this with some more general concept that also
-- works with streaming calls in the future. -- works with streaming calls in the future.
@ -189,7 +193,8 @@ serverHandleNormalRegisteredCall :: Server
-> (ByteString -> MetadataMap -> (ByteString -> MetadataMap
-> IO (ByteString, -> IO (ByteString,
MetadataMap, MetadataMap,
MetadataMap)) MetadataMap,
StatusDetails))
-- ^ Handler function takes a request body and -- ^ Handler function takes a request body and
-- metadata and returns a response body and -- metadata and returns a response body and
-- metadata. -- metadata.
@ -209,10 +214,10 @@ serverHandleNormalRegisteredCall s@Server{..} rm timeLimit initMetadata f = do
requestBody <- C.copyByteBufferToByteString payload requestBody <- C.copyByteBufferToByteString payload
metadataArray <- peek $ requestMetadataRecv call metadataArray <- peek $ requestMetadataRecv call
metadata <- C.getAllMetadataArray metadataArray metadata <- C.getAllMetadataArray metadataArray
(respBody, initMeta, trailingMeta) <- f requestBody metadata (respBody, initMeta, trailingMeta, details) <- f requestBody metadata
let status = C.GrpcStatusOk let status = C.GrpcStatusOk
let respOps = serverOpsSendNormalRegisteredResponse let respOps = serverOpsSendNormalRegisteredResponse
respBody initMeta trailingMeta status respBody initMeta trailingMeta status details
respOpsResults <- runOps call serverCQ respOps timeLimit respOpsResults <- runOps call serverCQ respOps timeLimit
grpcDebug "serverHandleNormalRegisteredCall: finished response ops." grpcDebug "serverHandleNormalRegisteredCall: finished response ops."
case respOpsResults of case respOpsResults of
@ -226,7 +231,7 @@ serverHandleNormalCall :: Server -> TimeoutSeconds
-> MetadataMap -> MetadataMap
-- ^ Initial metadata. -- ^ Initial metadata.
-> (ByteString -> MetadataMap -> (ByteString -> MetadataMap
-> IO (ByteString, MetadataMap)) -> IO (ByteString, MetadataMap, StatusDetails))
-- ^ Handler function takes a request body and -- ^ Handler function takes a request body and
-- metadata and returns a response body and metadata. -- metadata and returns a response body and metadata.
-> IO (Either GRPCIOError ()) -> IO (Either GRPCIOError ())
@ -239,9 +244,10 @@ serverHandleNormalCall s@Server{..} timeLimit initMetadata f = do
Left x -> return $ Left x Left x -> return $ Left x
Right [OpRecvMessageResult body] -> do Right [OpRecvMessageResult body] -> do
--TODO: we need to get client metadata --TODO: we need to get client metadata
(respBody, respMetadata) <- f body M.empty (respBody, respMetadata, details) <- f body M.empty
let status = C.GrpcStatusOk let status = C.GrpcStatusOk
let respOps = serverOpsSendNormalResponse respBody respMetadata status let respOps = serverOpsSendNormalResponse
respBody respMetadata status details
respOpsResults <- runOps call serverCQ respOps timeLimit respOpsResults <- runOps call serverCQ respOps timeLimit
case respOpsResults of case respOpsResults of
Left x -> do grpcDebug "serverHandleNormalCall: resp failed." Left x -> do grpcDebug "serverHandleNormalCall: resp failed."

View file

@ -1,3 +1,5 @@
{-# LANGUAGE StandaloneDeriving #-}
module Network.GRPC.Unsafe where module Network.GRPC.Unsafe where
import Control.Monad import Control.Monad
@ -20,9 +22,13 @@ import Network.GRPC.Unsafe.Constants
{#pointer *grpc_completion_queue as CompletionQueue newtype #} {#pointer *grpc_completion_queue as CompletionQueue newtype #}
deriving instance Show CompletionQueue
-- | Represents a connection to a server. Created on the client side. -- | Represents a connection to a server. Created on the client side.
{#pointer *grpc_channel as Channel newtype #} {#pointer *grpc_channel as Channel newtype #}
deriving instance Show Channel
-- | Represents a server. Created on the server side. -- | Represents a server. Created on the server side.
{#pointer *grpc_server as Server newtype #} {#pointer *grpc_server as Server newtype #}
@ -30,13 +36,11 @@ import Network.GRPC.Unsafe.Constants
-- type is abstract; we have no access to its fields. -- type is abstract; we have no access to its fields.
{#pointer *grpc_call as Call newtype #} {#pointer *grpc_call as Call newtype #}
instance Show Call where deriving instance Show Call
show (Call ptr) = show ptr
{#pointer *grpc_call_details as CallDetails newtype #} {#pointer *grpc_call_details as CallDetails newtype #}
instance Show CallDetails where deriving instance Show CallDetails
show (CallDetails ptr) = show ptr
{#fun create_call_details as ^ {} -> `CallDetails'#} {#fun create_call_details as ^ {} -> `CallDetails'#}
{#fun destroy_call_details as ^ {`CallDetails'} -> `()'#} {#fun destroy_call_details as ^ {`CallDetails'} -> `()'#}

View file

@ -1,3 +1,5 @@
{-# LANGUAGE StandaloneDeriving #-}
module Network.GRPC.Unsafe.ByteBuffer where module Network.GRPC.Unsafe.ByteBuffer where
#include <grpc/grpc.h> #include <grpc/grpc.h>
@ -21,6 +23,8 @@ import Foreign.Storable
-- Must be destroyed manually with 'grpcByteBufferDestroy'. -- Must be destroyed manually with 'grpcByteBufferDestroy'.
{#pointer *grpc_byte_buffer as ByteBuffer newtype #} {#pointer *grpc_byte_buffer as ByteBuffer newtype #}
deriving instance Show ByteBuffer
--Trivial Storable instance because 'ByteBuffer' type is a pointer. --Trivial Storable instance because 'ByteBuffer' type is a pointer.
instance Storable ByteBuffer where instance Storable ByteBuffer where
sizeOf (ByteBuffer r) = sizeOf r sizeOf (ByteBuffer r) = sizeOf r

View file

@ -1,3 +1,5 @@
{-# LANGUAGE StandaloneDeriving #-}
module Network.GRPC.Unsafe.Metadata where module Network.GRPC.Unsafe.Metadata where
import Control.Exception import Control.Exception
@ -18,6 +20,8 @@ import Foreign.Storable
-- is intended to be used when sending metadata. -- is intended to be used when sending metadata.
{#pointer *grpc_metadata as MetadataKeyValPtr newtype#} {#pointer *grpc_metadata as MetadataKeyValPtr newtype#}
deriving instance Show MetadataKeyValPtr
-- | Represents a pointer to a grpc_metadata_array. Must be destroyed with -- | Represents a pointer to a grpc_metadata_array. Must be destroyed with
-- 'metadataArrayDestroy'. This type is intended for receiving metadata. -- 'metadataArrayDestroy'. This type is intended for receiving metadata.
-- This can be populated by passing it to e.g. 'grpcServerRequestCall'. -- This can be populated by passing it to e.g. 'grpcServerRequestCall'.
@ -25,6 +29,8 @@ import Foreign.Storable
-- and length from this type. -- and length from this type.
{#pointer *grpc_metadata_array as MetadataArray newtype#} {#pointer *grpc_metadata_array as MetadataArray newtype#}
deriving instance Show MetadataArray
{#fun metadata_array_get_metadata as ^ {#fun metadata_array_get_metadata as ^
{`MetadataArray'} -> `MetadataKeyValPtr'#} {`MetadataArray'} -> `MetadataKeyValPtr'#}

View file

@ -1,3 +1,5 @@
{-# LANGUAGE StandaloneDeriving #-}
module Network.GRPC.Unsafe.Op where module Network.GRPC.Unsafe.Op where
import Control.Exception import Control.Exception
@ -34,6 +36,8 @@ import Foreign.Ptr
-- 'opArrayDestroy'. -- 'opArrayDestroy'.
{#pointer *grpc_op as OpArray newtype #} {#pointer *grpc_op as OpArray newtype #}
deriving instance Show OpArray
-- | Creates an empty 'OpArray' with space for the given number of ops. -- | Creates an empty 'OpArray' with space for the given number of ops.
{#fun op_array_create as ^ {`Int'} -> `OpArray'#} {#fun op_array_create as ^ {`Int'} -> `OpArray'#}
@ -98,5 +102,5 @@ withOpArray n f = bracket (opArrayCreate n) (flip opArrayDestroy n) f
-- Metadata and string are copied when creating the op, and can be safely -- Metadata and string are copied when creating the op, and can be safely
-- destroyed immediately after calling this function. -- destroyed immediately after calling this function.
{#fun op_send_status_server as ^ {#fun op_send_status_server as ^
{`OpArray', `Int', `Int', `MetadataKeyValPtr', `StatusCode', `String'} {`OpArray', `Int', `Int', `MetadataKeyValPtr', `StatusCode', `CString'}
-> `()'#} -> `()'#}

View file

@ -20,18 +20,17 @@ import Test.Tasty.HUnit as HU (testCase, (@?=))
lowLevelTests :: TestTree lowLevelTests :: TestTree
lowLevelTests = testGroup "Unit tests of low-level Haskell library" lowLevelTests = testGroup "Unit tests of low-level Haskell library"
[ testGRPCBracket [ testGRPCBracket
, testCompletionQueueCreateDestroy , testCompletionQueueCreateDestroy
, testServerCreateDestroy , testServerCreateDestroy
, testClientCreateDestroy , testClientCreateDestroy
, testWithServerCall , testWithServerCall
, testWithClientCall , testWithClientCall
-- , testPayloadLowLevel --TODO: currently crashing from free on unalloced ptr , testPayloadLowLevel
-- , testClientRequestNoServer --TODO: succeeds when no other tests run. , testClientRequestNoServer
, testServerAwaitNoClient , testServerAwaitNoClient
-- , testPayloadLowLevelUnregistered --TODO: succeeds when no other tests run. , testPayloadLowLevelUnregistered
, testPayload ]
]
dummyMeta :: M.Map ByteString ByteString dummyMeta :: M.Map ByteString ByteString
dummyMeta = M.fromList [("foo","bar")] dummyMeta = M.fromList [("foo","bar")]
@ -63,7 +62,9 @@ testPayloadLowLevelServer grpc = do
withServer grpc conf $ \server -> do withServer grpc conf $ \server -> do
let method = head (registeredMethods server) let method = head (registeredMethods server)
result <- serverHandleNormalRegisteredCall server method 11 M.empty $ result <- serverHandleNormalRegisteredCall server method 11 M.empty $
\_reqBody _reqMeta -> return ("reply test", dummyMeta, dummyMeta) \reqBody reqMeta ->
return ("reply test", dummyMeta, dummyMeta,
StatusDetails "details string")
case result of case result of
Left err -> error $ show err Left err -> error $ show err
Right _ -> return () Right _ -> return ()
@ -76,7 +77,8 @@ testPayloadLowLevelClient grpc =
reqResult <- clientRegisteredRequest client method 10 "Hello!" M.empty reqResult <- clientRegisteredRequest client method 10 "Hello!" M.empty
case reqResult of case reqResult of
Left x -> error $ "Client got error: " ++ show x Left x -> error $ "Client got error: " ++ show x
Right (NormalRequestResult respBody _initMeta _trailingMeta respCode) -> do Right (NormalRequestResult respBody initMeta trailingMeta respCode details) -> do
details @?= "details string"
respBody @?= "reply test" respBody @?= "reply test"
respCode @?= GrpcStatusOk respCode @?= GrpcStatusOk
@ -86,15 +88,18 @@ testPayloadLowLevelClientUnregistered grpc = do
reqResult <- clientRequest client "/foo" "localhost" 10 "Hello!" M.empty reqResult <- clientRequest client "/foo" "localhost" 10 "Hello!" M.empty
case reqResult of case reqResult of
Left x -> error $ "Client got error: " ++ show x Left x -> error $ "Client got error: " ++ show x
Right (NormalRequestResult respBody _initMeta _trailingMeta respCode) -> do Right (NormalRequestResult
respBody initMeta trailingMeta respCode details) -> do
respBody @?= "reply test" respBody @?= "reply test"
respCode @?= GrpcStatusOk respCode @?= GrpcStatusOk
details @?= "details string"
testPayloadLowLevelServerUnregistered :: GRPC -> IO () testPayloadLowLevelServerUnregistered :: GRPC -> IO ()
testPayloadLowLevelServerUnregistered grpc = do testPayloadLowLevelServerUnregistered grpc = do
withServer grpc (ServerConfig "localhost" 50051 []) $ \server -> do withServer grpc (ServerConfig "localhost" 50051 []) $ \server -> do
result <- serverHandleNormalCall server 11 M.empty $ result <- serverHandleNormalCall server 11 M.empty $
\_reqBody _reqMeta -> return ("reply test", M.empty) \reqBody reqMeta -> return ("reply test", M.empty,
StatusDetails "details string")
case result of case result of
Left x -> error $ show x Left x -> error $ show x
Right _ -> return () Right _ -> return ()
@ -114,7 +119,7 @@ testServerAwaitNoClient = testCase "server wait times out when no client " $ do
withServer grpc conf $ \server -> do withServer grpc conf $ \server -> do
let method = head (registeredMethods server) let method = head (registeredMethods server)
result <- serverHandleNormalRegisteredCall server method 1 M.empty $ result <- serverHandleNormalRegisteredCall server method 1 M.empty $
\_ _ -> return ("", M.empty, M.empty) \_ _ -> return ("", M.empty, M.empty, StatusDetails "details")
result @?= Left GRPCIOTimeout result @?= Left GRPCIOTimeout
testServerUnregisteredAwaitNoClient :: TestTree testServerUnregisteredAwaitNoClient :: TestTree
@ -124,7 +129,7 @@ testServerUnregisteredAwaitNoClient =
let conf = ServerConfig "localhost" 50051 [] let conf = ServerConfig "localhost" 50051 []
withServer grpc conf $ \server -> do withServer grpc conf $ \server -> do
result <- serverHandleNormalCall server 10 M.empty $ result <- serverHandleNormalCall server 10 M.empty $
\_ _ -> return ("", M.empty) \_ _ -> return ("", M.empty, StatusDetails "")
case result of case result of
Left err -> error $ show err Left err -> error $ show err
Right _ -> return () Right _ -> return ()

View file

@ -1,6 +1,251 @@
<<<<<<< HEAD
import LowLevelTests import LowLevelTests
import Test.Tasty import Test.Tasty
import UnsafeTests import UnsafeTests
=======
{-# LANGUAGE OverloadedStrings #-}
import Control.Concurrent.Async
import Network.GRPC.Unsafe
import Network.GRPC.Unsafe.Slice
import Network.GRPC.Unsafe.ByteBuffer
import Network.GRPC.Unsafe.Time
import Network.GRPC.Unsafe.Metadata
import Network.GRPC.Unsafe.Op
import Network.GRPC.Unsafe.Constants
import qualified Data.ByteString as B
import Foreign.Marshal.Alloc
import Foreign.Storable
import Foreign.Ptr
import Test.Tasty
import Test.Tasty.HUnit as HU
import LowLevelTests
roundtripSlice :: B.ByteString -> TestTree
roundtripSlice bs = testCase "Slice C bindings roundtrip" $ do
slice <- byteStringToSlice bs
unslice <- sliceToByteString slice
bs HU.@?= unslice
freeSlice slice
roundtripByteBuffer :: B.ByteString -> TestTree
roundtripByteBuffer bs = testCase "ByteBuffer C bindings roundtrip" $ do
slice <- byteStringToSlice bs
buffer <- grpcRawByteBufferCreate slice 1
reader <- byteBufferReaderCreate buffer
readSlice <- grpcByteBufferReaderReadall reader
bs' <- sliceToByteString readSlice
bs' HU.@?= bs
--clean up
freeSlice slice
byteBufferReaderDestroy reader
grpcByteBufferDestroy buffer
freeSlice readSlice
currTimeMillis :: ClockType -> IO Int
currTimeMillis t = do
gprT <- gprNow t
tMillis <- gprTimeToMillis gprT
timespecDestroy gprT
return tMillis
testNow :: TestTree
testNow = testCase "create and destroy various clock types" $ do
_ <- currTimeMillis GprClockMonotonic
_ <- currTimeMillis GprClockRealtime
_ <- currTimeMillis GprClockPrecise
return ()
testMetadata :: TestTree
testMetadata = testCase "metadata setter/getter C bindings roundtrip" $ do
m <- metadataAlloc 3
setMetadataKeyVal "hello" "world" m 0
setMetadataKeyVal "foo" "bar" m 1
setMetadataKeyVal "Haskell" "Curry" m 2
k0 <- getMetadataKey m 0
v0 <- getMetadataVal m 0
k1 <- getMetadataKey m 1
v1 <- getMetadataVal m 1
k2 <- getMetadataKey m 2
v2 <- getMetadataVal m 2
k0 HU.@?= "hello"
v0 HU.@?= "world"
k1 HU.@?= "foo"
v1 HU.@?= "bar"
k2 HU.@?= "Haskell"
v2 HU.@?= "Curry"
metadataFree m
assertCqEventComplete :: Event -> IO ()
assertCqEventComplete e = do
eventCompletionType e HU.@?= OpComplete
eventSuccess e HU.@?= True
testPayloadClient :: IO ()
testPayloadClient = do
client <- grpcInsecureChannelCreate "localhost:50051" nullPtr reserved
cq <- grpcCompletionQueueCreate reserved
withMetadataArrayPtr $ \initialMetadataRecv -> do
withMetadataArrayPtr $ \trailingMetadataRecv -> do
withByteBufferPtr $ \clientRecvBB -> do
deadline <- secondsToDeadline 5
pluckDeadline <- secondsToDeadline 10
clientCall <- grpcChannelCreateCall
client (Call nullPtr) propagateDefaults cq
"/foo" "localhost" deadline reserved
--send request
withOpArray 6 $ \ops -> do
opSendInitialMetadataEmpty ops 0
withByteStringAsByteBuffer "hello world" $ \requestPayload -> do
opSendMessage ops 1 requestPayload
opSendCloseClient ops 2
opRecvInitialMetadata ops 3 initialMetadataRecv
opRecvMessage ops 4 clientRecvBB
statusCodePtr <- createStatusCodePtr
let cstringCapacity = 32
cStringPtr <- malloc
cstring <- mallocBytes cstringCapacity
poke cStringPtr cstring
opRecvStatusClient ops 5 trailingMetadataRecv statusCodePtr
cStringPtr
cstringCapacity
--send client request
requestError <- grpcCallStartBatch clientCall ops 6 (tag 1) reserved
clientRequestCqEvent <- grpcCompletionQueuePluck
cq (tag 1) pluckDeadline reserved
assertCqEventComplete clientRequestCqEvent
requestError HU.@?= CallOk
free cstring
free cStringPtr
destroyStatusCodePtr statusCodePtr
--verify response received
responseRecv <- peek clientRecvBB
responseRecvBS <- copyByteBufferToByteString responseRecv
responseRecvBS HU.@?= "hello you"
grpcCompletionQueueShutdown cq
grpcCallDestroy clientCall
--TODO: the grpc test drains the cq here
grpcCompletionQueueDestroy cq
grpcChannelDestroy client
testPayloadServer :: IO ()
testPayloadServer = do
server <- grpcServerCreate nullPtr reserved
cq <- grpcCompletionQueueCreate reserved
grpcServerRegisterCompletionQueue server cq reserved
_ <- grpcServerAddInsecureHttp2Port server "localhost:50051"
grpcServerStart server
serverCallPtr <- malloc
withMetadataArrayPtr $ \requestMetadataRecv -> do
withByteBufferPtr $ \recvBufferPtr -> do
callDetails <- createCallDetails
requestMetadataRecv' <- peek requestMetadataRecv
recvRequestError <- grpcServerRequestCall
server serverCallPtr callDetails
requestMetadataRecv' cq cq (tag 101)
pluckDeadline' <- secondsToDeadline 10
requestCallCqEvent <- grpcCompletionQueuePluck cq (tag 101)
pluckDeadline'
reserved
assertCqEventComplete requestCallCqEvent
recvRequestError HU.@?= CallOk
destroyCallDetails callDetails
--receive request
withOpArray 2 $ \recvOps -> do
opSendInitialMetadataEmpty recvOps 0
opRecvMessage recvOps 1 recvBufferPtr
serverCall <- peek serverCallPtr
recvBatchError <- grpcCallStartBatch serverCall recvOps 2
(tag 102) reserved
recvBatchError HU.@?= CallOk
pluckDeadline'' <- secondsToDeadline 10
recvCqEvent <- grpcCompletionQueuePluck cq (tag 102)
pluckDeadline''
reserved
assertCqEventComplete recvCqEvent
--send response
withOpArray 3 $ \respOps -> do
withByteStringAsByteBuffer "hello you" $ \respbb -> do
cancelledPtr <- malloc
opRecvCloseServer respOps 0 cancelledPtr
opSendMessage respOps 1 respbb
B.useAsCString "ok" $ \detailsStr ->
opSendStatusServer respOps 2 0 (MetadataKeyValPtr nullPtr)
GrpcStatusOk detailsStr
serverCall <- peek serverCallPtr
respBatchError <- grpcCallStartBatch serverCall respOps 3
(tag 103) reserved
respBatchError HU.@?= CallOk
pluckDeadline''' <- secondsToDeadline 10
respCqEvent <- grpcCompletionQueuePluck cq (tag 103)
pluckDeadline'''
reserved
assertCqEventComplete respCqEvent
--verify data was received
serverRecv <- peek recvBufferPtr
serverRecvBS <- copyByteBufferToByteString serverRecv
serverRecvBS HU.@?= "hello world"
--shut down
grpcServerShutdownAndNotify server cq (tag 0)
pluckDeadline'''' <- secondsToDeadline 10
shutdownEvent <- grpcCompletionQueuePluck cq (tag 0) pluckDeadline''''
reserved
assertCqEventComplete shutdownEvent
grpcServerCancelAllCalls server
grpcServerDestroy server
grpcCompletionQueueShutdown cq
grpcCompletionQueueDestroy cq
free serverCallPtr
-- | Straightforward translation of the gRPC core test end2end/tests/payload.c
-- This is intended to test the low-level C bindings, so we use only a few
-- minimal abstractions on top of it.
testPayload :: TestTree
testPayload = testCase "low-level C bindings request/response " $ do
grpcInit
withAsync testPayloadServer $ \a1 -> do
withAsync testPayloadClient $ \a2 -> do
() <- wait a1
wait a2
grpcShutdown
putStrLn "Done."
testCreateDestroyMetadata :: TestTree
testCreateDestroyMetadata = testCase "create/destroy metadataArrayPtr " $ do
grpcInit
withMetadataArrayPtr $ const (return ())
grpcShutdown
testCreateDestroyMetadataKeyVals :: TestTree
testCreateDestroyMetadataKeyVals = testCase "create/destroy metadata k/vs " $ do
grpcInit
withMetadataKeyValPtr 10 $ const (return ())
grpcShutdown
testCreateDestroyDeadline :: TestTree
testCreateDestroyDeadline = testCase "create/destroy deadline " $ do
grpcInit
withDeadlineSeconds 10 $ const (return ())
grpcShutdown
unsafeTests :: TestTree
unsafeTests = testGroup "Unit tests for unsafe C bindings."
[testPayload,
roundtripSlice "Hello, world!",
roundtripByteBuffer "Hwaet! We gardena in geardagum...",
testMetadata,
testNow,
testCreateDestroyMetadata,
testCreateDestroyMetadataKeyVals,
testCreateDestroyDeadline
]
allTests :: TestTree
allTests = testGroup "All tests"
[ unsafeTests,
lowLevelTests]
>>>>>>> Fix payload test bugs (#8)
main :: IO () main :: IO ()
main = defaultMain $ testGroup "GRPC Unit Tests" main = defaultMain $ testGroup "GRPC Unit Tests"