packages feed

hcoap-0.1.2.0: test/Spec.hs

import Network.CoAP.Message
import qualified Network.CoAP.Server as S
import qualified Network.CoAP.Client as C
import Network.CoAP.Transport
import Network.Socket hiding (sendTo, recvFrom)
import Test.HUnit
import Data.ByteString.Char8 hiding (putStrLn)
import Test.QuickCheck
import Prelude hiding (null, length)
import Control.Concurrent.Chan
import Control.Concurrent.Async
import Control.Monad
import System.Random
import System.Timeout
import Data.Maybe

data Cluster = Cluster Transport Transport

tests = TestList [TestLabel "testReliability" testReliability]

sendToChan prob chan msg endpoint = do
  v <- randomRIO(0, 1)
  when (v >= prob) (putStrLn ("Message to to " ++ show endpoint ++ " dropped!"))
  when (v < prob) (writeChan chan msg)
  return (length msg)

createUnstableTransport :: Double -> (Endpoint, Chan ByteString) -> (Endpoint, Chan ByteString) -> Transport
createUnstableTransport prob (localE, localC) (remoteE, remoteC) =
  Transport { sendTo = sendToChan prob remoteC
            , recvFrom = do
                d <- readChan localC
                return (d, remoteE)
            , localEndpoint = return localE }

testHandler :: S.RequestHandler
testHandler (req, _) = return (S.Response S.Created [] (Just (pack "Hello, Client")))

instance Arbitrary C.Request where
  arbitrary = do
    pload <- arbitrary
    return C.Request { C.requestMethod = GET
                     , C.requestOptions = []
                     , C.requestPayload = pload
                     , C.requestReliable = True }

testReliability =
  TestCase (do
    linkAProb <- randomRIO(0.7, 1)
    linkBProb <- randomRIO(0.7, 1)
    chanA <- newChan
    chanB <- newChan
    let endpointA = SockAddrUnix "A"
    let endpointB = SockAddrUnix "B"
    let transportA = createUnstableTransport linkAProb (endpointA, chanA) (endpointB, chanB)
    let transportB = createUnstableTransport linkBProb (endpointB, chanB) (endpointA, chanA)
    server <- S.createServer transportA testHandler
    serverThread <- async (S.runServer server)
    client <- C.createClient transportB 
    reqs <- generate (vector 10)
    mapM_ (\req -> do
      response <- timeout 20000000 (C.doRawRequest client endpointA req)
      assertBool ("Timed out waiting for response on reliable request " ++ show req) (isJust response)
      let res = fromJust response
      putStrLn "Got response, checking"
      assertEqual "Bad response code" C.Created (C.responseCode res)
      assertEqual "Bad payload" "Hello, Client" (unpack (fromJust (C.responsePayload res)))
      ) reqs
    S.stopServer server)

-- TODO, negative tests:
-- * Invalid token length
-- * Invalid code
-- * Unknown options
-- * Bad payload marker

instance Arbitrary MessageType where
  arbitrary = elements [CON, NON, ACK, RST]

instance Arbitrary Method where
  arbitrary = elements [PUT, GET, POST, DELETE]

instance Arbitrary ByteString where
  arbitrary = suchThat (fmap pack arbitrary) (\s -> (length s > 0) && (length s <= 8))

instance Arbitrary MediaType where
  arbitrary = elements [TextPlain, ApplicationLinkFormat, ApplicationXml, ApplicationOctetStream, ApplicationExi, ApplicationJson]

instance Arbitrary Option where
  arbitrary = do
    mediaType <- arbitrary
    etag <- arbitrary
    loc <- arbitrary
    locquery <- arbitrary
    puri <- arbitrary
    pscheme <- arbitrary
    uriHost <- arbitrary
    uriPath <- arbitrary
    uriQuery <- arbitrary
    ifMatch <- arbitrary
    elements [ContentFormat mediaType, ETag etag, LocationPath loc, LocationQuery locquery, ProxyUri puri, ProxyScheme pscheme, UriHost uriHost, UriPath uriPath, UriQuery uriQuery, IfMatch ifMatch]

instance Arbitrary Message where
  arbitrary = do
    msgType <- arbitrary
    msgMethod <- arbitrary
    msgId <- arbitrary
    tkn <- arbitrary
    options <- arbitrary
    payload <- arbitrary
    return Message { messageVersion = 1
                   , messageType    = msgType
                   , messageCode    = CodeRequest msgMethod
                   , messageId      = msgId
                   , messageToken = tkn
                   , messageOptions = [options]
                   , messagePayload = payload }


checkCodec msg =
  let encoded = encode msg
      (Right decoded) = decode encoded
   in show msg == show decoded

main :: IO ()
main = do
  putStrLn "Running HUnit tests"
  runTestTT tests
  putStrLn "Running QuickCheck tests"
  quickCheck checkCodec
  return ()