purescript-iso-0.0.1.5: src/Test/Serialization.hs
{-# LANGUAGE
OverloadedStrings
, OverloadedLists
, RecordWildCards
, ScopedTypeVariables
, DataKinds
, NamedFieldPuns
#-}
module Test.Serialization where
import Test.Serialization.Types
( TestSuiteM, MsgType (..), ServerToClient (..), TestTopic
, ClientToServer (..), TestSuiteState, TestTopicState (..), emptyTestSuiteState
, gotClientGenValue, serializeValueClientOrigin, getTopicState
, gotClientSerialize, gotClientDeSerialize
, deserializeValueClientOrigin, verify, generateValue, getTopicState
, HasTopic (..), DesValue (..), HasClientG (..), GenValue (..)
, HasClientS (..), isOkay)
import Data.URI (URI (..), printURI)
import Data.URI.Auth (URIAuth)
import Data.UUID (UUID)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.UTF8 as BS8
import qualified Data.Strict.Maybe as Strict
import Data.List.NonEmpty (NonEmpty (..))
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Aeson (eitherDecode, encode, toJSON)
import Control.Monad (forever, void, when)
import Control.Monad.Reader (runReaderT)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Control (liftBaseWith)
import Control.Concurrent.Async (async, link, cancel)
import Control.Concurrent.STM
(STM, TVar, newTVar, atomically, modifyTVar, readTVar, writeTVar)
import System.ZMQ4.Monadic
(ZMQ, runZMQ, Router (..), Dealer (..))
import qualified System.ZMQ4.Simple as Z
import System.Exit (exitSuccess)
data ServerParams = ServerParams
{ serverParamsControlHost :: URIAuth
, serverParamsTestSuite :: TestSuiteM ()
, serverParamsMaxSize :: Int
}
type TopicsPending = TVar (Set TestTopic)
-- TODO client threads?
type ServerState = TVar (Map Z.ZMQIdent (TestSuiteState, TopicsPending))
emptyServerState :: STM ServerState
emptyServerState = newTVar Map.empty
startServer :: ServerParams -> IO ()
startServer ServerParams{..} = do
runZMQ $ do
server <- Z.socket Router Dealer
Z.bind server $ T.unpack $ printURI $
URI (Strict.Just "tcp") True serverParamsControlHost Strict.Nothing [] Strict.Nothing
serverStateRef <- liftIO (atomically emptyServerState)
liftIO $ do
suiteState <- atomically emptyTestSuiteState
runReaderT serverParamsTestSuite suiteState
ts <- Map.keysSet <$> atomically (readTVar suiteState)
when (ts == Set.empty) exitSuccess
forever $ do
mIncoming <- Z.receive server
case mIncoming of
Nothing -> liftIO $ putStrLn $ "No incoming message?"
Just (addr :: Z.ZMQIdent, incoming :| _) -> case eitherDecode (LBS.fromStrict incoming) of
Left e -> do
let err = LBS.toStrict $ encode $ ServerToClientBadParse $ T.decodeUtf8 incoming
Z.send addr server (err :| [])
error $ "Couldn't parse, sending `BadParse`: " ++ BS8.toString incoming ++ ", json error: " ++ e ++ ", original: " ++ BS8.toString incoming
Right (x :: ClientToServer) -> case x of
GetTopics -> do
ts <- liftIO $ registerClient serverParamsTestSuite serverStateRef addr
() <$ send addr server (TopicsAvailable ts)
ClientToServerBadParse e -> error $ T.unpack e
Finished t -> do
liftIO $ putStrLn $ "success: " ++ show t -- FIXME compile report?
mX <- liftIO $ atomically $ Map.lookup addr <$> readTVar serverStateRef
case mX of
Nothing -> error $ "Received `Finished` from nonexistent topic? " ++ show t
Just (_, pendingTopicsRef) -> do
ts <- liftIO $ atomically $ readTVar pendingTopicsRef
if ts == Set.empty || ts == Set.singleton t
then liftIO exitSuccess
else liftIO $ atomically $ modifyTVar pendingTopicsRef $ Set.delete t
-- order:
-- clientG
-- serverS
-- clientD
-- serverG
-- clientS
-- serverD
-- verify
ClientToServer t m y -> do
mSuiteState <- liftIO $ atomically $ getTestSuiteState serverStateRef addr
case mSuiteState of
Nothing -> error "No test suite state!"
Just suiteState -> do
mState <- liftIO $ getTopicState suiteState t
case mState of
NoTopic -> error "No topic"
HasTopic state -> case m of
Failure -> do
liftIO $ dumpTopic serverStateRef addr t
error $ "Failure: " ++ show t ++ ", " ++ show y -- TODO compile report
GeneratedInput -> do
liftIO $ atomically $ writeTVar (clientGReceived state) (Just incoming)
mOk <- liftIO $ gotClientGenValue state y
if isOkay mOk
then do
mOutgoing <- liftIO $ serializeValueClientOrigin state t
case mOutgoing of
HasClientG outgoing -> do
o' <- send addr server outgoing
liftIO $ atomically $ writeTVar (serverSSent state) (Just o')
_ -> fail' serverStateRef server "Bad serialize: " addr t mOutgoing
else fail' serverStateRef server "Bad got gen: " addr t mOk
DeSerialized -> do
liftIO $ atomically $ writeTVar (clientDReceived state) (Just incoming)
mOk <- liftIO $ gotClientDeSerialize state y
if isOkay mOk
then do
mOutgoing <- liftIO $ generateValue state t serverParamsMaxSize
case mOutgoing of
GenValue outgoing -> do
o' <- send addr server outgoing
liftIO $ atomically $ writeTVar (serverGSent state) (Just o')
DoneGenerating -> error "Server finished before client"
-- FIXME should never occur - client dictates
-- number of quickchecks
else fail' serverStateRef server "Bad got deserialize: " addr t mOk
Serialized -> do
liftIO $ atomically $ writeTVar (clientSReceived state) (Just incoming)
mOk <- liftIO $ gotClientSerialize state y
if isOkay mOk
then do
mOutgoing <- liftIO $ deserializeValueClientOrigin state t
case mOutgoing of
HasClientS (DesValue outgoing) -> do
o' <- send addr server outgoing
liftIO $ atomically $ writeTVar (serverDSent state) (Just o')
-- verify
mOk' <- liftIO $ verify state
if isOkay mOk'
then do
liftIO $ clearState state
() <$ send addr server (Continue t)
else fail' serverStateRef server "Bad verify: " addr t mOk'
_ -> fail' serverStateRef server "Bad deserialize value: " addr t mOutgoing
else fail' serverStateRef server "Bad got serialize: " addr t mOk
clearState :: TestTopicState
-> IO ()
clearState TestTopicState{..} = atomically $ do
writeTVar clientG Nothing
writeTVar clientGReceived Nothing
writeTVar clientS Nothing
writeTVar clientSReceived Nothing
writeTVar clientD Nothing
writeTVar clientDReceived Nothing
writeTVar serverG Nothing
writeTVar serverGSent Nothing
writeTVar serverS Nothing
writeTVar serverSSent Nothing
writeTVar serverD Nothing
writeTVar serverDSent Nothing
getTestSuiteState :: ServerState -> Z.ZMQIdent -> STM (Maybe TestSuiteState)
getTestSuiteState serverState clientKey =
fmap fst . Map.lookup clientKey <$> readTVar serverState
registerClient :: TestSuiteM ()
-> ServerState
-> Z.ZMQIdent
-> IO (Set TestTopic)
registerClient tests serverState clientKey = do
ss <- atomically $ readTVar serverState
case Map.lookup clientKey ss of
Just _ -> error "Client key already taken"
Nothing -> do
suiteState <- atomically emptyTestSuiteState
runReaderT tests suiteState
ks <- Map.keysSet <$> atomically (readTVar suiteState)
y <- atomically $ newTVar ks
atomically $ modifyTVar serverState (Map.insert clientKey (suiteState,y))
pure ks
fail' :: Show a
=> ServerState
-> Z.Socket z Router Dealer Z.Bound
-> String
-> Z.ZMQIdent
-> TestTopic
-> a
-> ZMQ z ()
fail' serverStateRef server prefix addr t v = do
liftIO $ dumpTopic serverStateRef addr t
_ <- send addr server $ ServerToClient t Failure $ toJSON $ show v
error $ prefix ++ show t ++ ", " ++ show v
send :: Z.ZMQIdent -> Z.Socket z Router Dealer Z.Bound -> ServerToClient -> ZMQ z ByteString
send addr server x = do
let x' = LBS.toStrict (encode x)
Z.send addr server (x' :| [])
pure x'
dumpTopic :: ServerState
-> Z.ZMQIdent
-> TestTopic
-> IO ()
dumpTopic serverStateRef addr t = do
mSuiteState <- liftIO $ atomically $ getTestSuiteState serverStateRef addr
case mSuiteState of
Nothing -> error "No test suite state!"
Just suiteState -> do
mState <- getTopicState suiteState t
case mState of
NoTopic -> error $ "No topic in test suite! " ++ show t
HasTopic (TestTopicState {..}) -> do
size' <- atomically $ readTVar size
putStrLn $ "size: " ++ show size'
mClientG <- atomically (readTVar clientG)
putStrLn $ "clientG: " ++ show (serialize <$> mClientG)
mBS <- atomically (readTVar clientGReceived)
putStrLn $ " - received: " ++ show mBS
mServerS <- atomically (readTVar serverS)
putStrLn $ "serverS: " ++ show mServerS
mBS <- atomically (readTVar serverSSent)
putStrLn $ " - sent: " ++ show mBS
mClientD <- atomically (readTVar clientD)
putStrLn $ "clientD: " ++ show (serialize <$> mClientD)
mBS <- atomically (readTVar clientDReceived)
putStrLn $ " - received: " ++ show mBS
mServerG <- atomically (readTVar serverG)
putStrLn $ "serverG: " ++ show (serialize <$> mServerG)
mBS <- atomically (readTVar serverGSent)
putStrLn $ " - sent: " ++ show mBS
mClientS <- atomically (readTVar clientS)
putStrLn $ "clientS: " ++ show mClientS
mBS <- atomically (readTVar clientSReceived)
putStrLn $ " - received: " ++ show mBS
mServerD <- atomically (readTVar serverD)
putStrLn $ "serverD: " ++ show (serialize <$> mServerD)
mBS <- atomically (readTVar serverDSent)
putStrLn $ " - sent: " ++ show mBS