purescript-iso-0.0.1.1: src/Test/Serialization/Types.hs
{-# LANGUAGE
DeriveGeneric
, OverloadedStrings
, GADTs
, RankNTypes
, ScopedTypeVariables
, NamedFieldPuns
, RecordWildCards
, ExistentialQuantification
, GeneralizedNewtypeDeriving
#-}
module Test.Serialization.Types where
import Data.Text (Text)
import qualified Data.Text as T
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.UUID (UUID)
import Data.Aeson
(FromJSON (..), ToJSON (..), object, (.:), (.=), Value (..))
import Data.Aeson.Types (typeMismatch, Parser, parseEither)
import Data.Proxy (Proxy (..))
import Data.String (IsString)
import Control.Applicative ((<|>))
import Control.Monad.Reader (ReaderT, ask)
import Control.Monad.IO.Class (liftIO)
import Control.Concurrent.STM
( STM, TVar, newTVar, atomically, modifyTVar, readTVar
, writeTVar)
import Test.QuickCheck (Arbitrary (..))
import Test.QuickCheck.Gen (Gen, unGen, oneof, listOf1, elements, scale)
import Test.QuickCheck.Random (newQCGen)
import Test.QuickCheck.Instances ()
import GHC.Generics (Generic)
newtype TestTopic = TestTopic Text
deriving (IsString, Eq, Ord, Generic, Show, ToJSON, FromJSON)
instance Arbitrary TestTopic where
arbitrary = TestTopic . T.pack <$> listOf1 (elements ['a' .. 'z'])
instance Arbitrary Value where
arbitrary =
oneof
[ termNull
, termNumber
, termBool
, termString
, scale (`div` 2) (Array <$> arbitrary)
, scale (`div` 2) (Object <$> arbitrary)
]
where
termNull = pure Null
termNumber = Number <$> arbitrary
termBool = Bool <$> arbitrary
termString = String <$> arbitraryNonEmptyText
arbitraryNonEmptyText = T.pack <$> listOf1 (elements ['a' .. 'z'])
data ChannelMsg
= GeneratedInput TestTopic Value
| Serialized TestTopic Value
| DeSerialized TestTopic Value
| Failure TestTopic Value
deriving (Eq, Show, Generic)
instance Arbitrary ChannelMsg where
arbitrary = oneof
[ GeneratedInput <$> arbitrary <*> arbitrary -- Json
, Serialized <$> arbitrary <*> arbitrary -- Json
, DeSerialized <$> arbitrary <*> arbitrary -- Json
, Failure <$> arbitrary <*> arbitrary -- Json
]
-- where
-- arbitraryJson = pure (String "foo")
instance ToJSON ChannelMsg where
toJSON x = case x of
GeneratedInput t y -> object ["topic" .= t, "generated" .= y]
Serialized t y -> object ["topic" .= t, "serialized" .= y]
DeSerialized t y -> object ["topic" .= t, "deserialized" .= y]
Failure t y -> object ["topic" .= t, "failure" .= y]
instance FromJSON ChannelMsg where
parseJSON x = case x of
Object o -> do
let gen = GeneratedInput <$> o .: "topic" <*> o .: "generated"
ser = Serialized <$> o .: "topic" <*> o .: "serialized"
des = DeSerialized <$> o .: "topic" <*> o .: "deserialized"
fai = Failure <$> o .: "topic" <*> o .: "failure"
gen <|> ser <|> des <|> fai
_ -> typeMismatch "ChannelMsg" x
data ClientToServer
= GetTopics
| ClientToServer ChannelMsg
| ClientToServerBadParse Text
| Finished TestTopic
deriving (Eq, Show, Generic)
instance Arbitrary ClientToServer where
arbitrary = oneof
[ pure GetTopics
, ClientToServer <$> arbitrary
, ClientToServerBadParse <$> arbitraryNonEmptyText
, Finished <$> arbitrary
]
where
arbitraryNonEmptyText = T.pack <$> listOf1 (elements ['a' .. 'z'])
instance ToJSON ClientToServer where
toJSON x = case x of
GetTopics -> String "getTopics"
ClientToServer y -> object ["channelMsg" .= y]
ClientToServerBadParse y -> object ["badParse" .= y]
Finished y -> object ["finished" .= y]
instance FromJSON ClientToServer where
parseJSON json = case json of
Object o -> do
let chn = ClientToServer <$> o .: "channelMsg"
bd = ClientToServerBadParse <$> o .: "badParse"
fin = Finished <$> o .: "finished"
chn <|> bd <|> fin
String s
| s == "getTopics" -> pure GetTopics
| otherwise -> fail'
_ -> fail'
where
fail' = typeMismatch "ClientToServer" json
data ServerToClient
= TopicsAvailable (Set TestTopic)
| ServerToClient ChannelMsg
| ServerToClientBadParse Text
| Continue TestTopic
deriving (Eq, Show, Generic)
instance Arbitrary ServerToClient where
arbitrary = oneof
[ TopicsAvailable <$> arbitrary
, ServerToClient <$> arbitrary
, ServerToClientBadParse <$> arbitraryNonEmptyText
, Continue <$> arbitrary
]
where
arbitraryNonEmptyText = T.pack <$> listOf1 (elements ['a' .. 'z'])
instance ToJSON ServerToClient where
toJSON x = case x of
TopicsAvailable xs -> object ["topics" .= Set.toList xs]
ServerToClient y -> object ["channelMsg" .= y]
ServerToClientBadParse x -> object ["badParse" .= x]
Continue y -> object ["continue" .= y]
instance FromJSON ServerToClient where
parseJSON x = case x of
Object o -> do
let available = TopicsAvailable . Set.fromList <$> o .: "topics"
badParse = ServerToClientBadParse <$> o .: "badParse"
channel = ServerToClient <$> o .: "channelMsg"
continue = Continue <$> o .: "continue"
available <|> badParse <|> channel <|> continue
_ -> fail'
where
fail' = typeMismatch "ServerToClientControl" x
data TestTopicState =
forall a
. ( Arbitrary a
, ToJSON a
, FromJSON a
, Eq a
) => TestTopicState
{ generate :: Gen a
, serialize :: a -> Value
, deserialize :: Value -> Parser a
, size :: TVar Int
, serverG :: TVar (Maybe a)
, clientS :: TVar (Maybe Value)
, serverD :: TVar (Maybe a)
, clientG :: TVar (Maybe a)
, serverS :: TVar (Maybe Value)
, clientD :: TVar (Maybe a)
}
emptyTestTopicState :: forall a
. ( Arbitrary a
, ToJSON a
, FromJSON a
, Eq a
) => Proxy a -> STM TestTopicState
emptyTestTopicState Proxy = do
size <- newTVar 1
(serverG :: TVar (Maybe a)) <- newTVar Nothing
clientS <- newTVar Nothing
(serverD :: TVar (Maybe a)) <- newTVar Nothing
(clientG :: TVar (Maybe a)) <- newTVar Nothing
serverS <- newTVar Nothing
(clientD :: TVar (Maybe a)) <- newTVar Nothing
pure TestTopicState
{ size
, serverG
, clientS
, serverD
, clientG
, serverS
, clientD
, generate = arbitrary
, serialize = toJSON
, deserialize = parseJSON
}
type TestSuiteState = TVar (Map TestTopic TestTopicState)
emptyTestSuiteState :: STM TestSuiteState
emptyTestSuiteState = newTVar Map.empty
type TestSuiteM a = ReaderT TestSuiteState IO a
registerTopic :: forall a
. ( Arbitrary a
, ToJSON a
, FromJSON a
, Eq a
) => TestTopic
-> Proxy a
-> TestSuiteM ()
registerTopic topic p = do
xsRef <- ask
liftIO $ atomically $ do
state <- emptyTestTopicState p
modifyTVar xsRef (Map.insert topic state)
class IsOkay a where
isOkay :: a -> Bool
instance IsOkay () where
isOkay () = True
data HasTopic a
= HasTopic a
| NoTopic
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (HasTopic a) where
isOkay x = case x of
NoTopic -> False
HasTopic y -> isOkay y
data GenValue a
= DoneGenerating
| GenValue a
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (GenValue a) where
isOkay x = case x of
DoneGenerating -> False
GenValue y -> isOkay y
data GotClientGenValue a
= NoClientGenValue
| GotClientGenValue a
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (GotClientGenValue a) where
isOkay x = case x of
NoClientGenValue -> False
GotClientGenValue y -> isOkay y
data HasClientG a
= NoClientG
| HasClientG a
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (HasClientG a) where
isOkay x = case x of
NoClientG -> False
HasClientG y -> isOkay y
data HasServerG a
= NoServerG
| HasServerG a
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (HasServerG a) where
isOkay x = case x of
NoServerG -> False
HasServerG y -> isOkay y
data HasServerS a
= NoServerS
| HasServerS a
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (HasServerS a) where
isOkay x = case x of
NoServerS -> False
HasServerS y -> isOkay y
data HasServerD a
= NoServerD
| HasServerD a
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (HasServerD a) where
isOkay x = case x of
NoServerD -> False
HasServerD y -> isOkay y
data HasClientD a
= NoClientD
| HasClientD a
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (HasClientD a) where
isOkay x = case x of
NoClientD -> False
HasClientD y -> isOkay y
data DesValue a
= CantDes String
| DesValue a
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (DesValue a) where
isOkay x = case x of
CantDes _ -> False
DesValue y -> isOkay y
data HasClientS a
= NoClientS
| HasClientS a
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (HasClientS a) where
isOkay x = case x of
NoClientS -> False
HasClientS y -> isOkay y
data ServerSerializedMatch a
= ServerSerializedMatch a
| ServerSerializedMismatch
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (ServerSerializedMatch a) where
isOkay x = case x of
ServerSerializedMismatch -> False
ServerSerializedMatch y -> isOkay y
data ServerDeSerializedMatch a
= ServerDeSerializedMatch a
| ServerDeSerializedMismatch
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (ServerDeSerializedMatch a) where
isOkay x = case x of
ServerDeSerializedMismatch -> False
ServerDeSerializedMatch y -> isOkay y
data ClientSerializedMatch a
= ClientSerializedMatch a
| ClientSerializedMismatch
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (ClientSerializedMatch a) where
isOkay x = case x of
ClientSerializedMismatch -> False
ClientSerializedMatch y -> isOkay y
data ClientDeSerializedMatch a
= ClientDeSerializedMatch a
| ClientDeSerializedMismatch
deriving (Eq, Show, Generic)
instance IsOkay a => IsOkay (ClientDeSerializedMatch a) where
isOkay x = case x of
ClientDeSerializedMismatch -> False
ClientDeSerializedMatch y -> isOkay y
getTopicState :: TestSuiteState
-> TestTopic
-> IO (HasTopic TestTopicState)
getTopicState xsRef topic = do
xs <- atomically (readTVar xsRef)
case Map.lookup topic xs of
Nothing -> pure NoTopic
Just x -> pure (HasTopic x)
generateValue :: TestSuiteState
-> TestTopic
-> IO (HasTopic (GenValue ChannelMsg))
generateValue xsRef topic = do
mState <- getTopicState xsRef topic
case mState of
NoTopic -> pure NoTopic
HasTopic (TestTopicState{size,generate,serialize,serverG}) -> fmap HasTopic $ do
s <- atomically (readTVar size)
if s >= 100
then pure DoneGenerating
else do
g <- newQCGen
let val = unGen generate g s
atomically $ do
modifyTVar size (+ 1)
writeTVar serverG $ Just val
pure $ GenValue $ GeneratedInput topic $ serialize val
gotClientGenValue :: TestSuiteState
-> TestTopic
-> Value
-> IO (HasTopic (DesValue ()))
gotClientGenValue xsRef topic value = do
mState <- getTopicState xsRef topic
case mState of
NoTopic -> pure NoTopic
HasTopic (TestTopicState{deserialize,clientG}) -> fmap HasTopic $ do
case parseEither deserialize value of
Left e -> pure (CantDes e)
Right y -> do
atomically $ writeTVar clientG $ Just y
pure (DesValue ())
serializeValueClientOrigin :: TestSuiteState
-> TestTopic
-> IO (HasTopic (HasClientG ChannelMsg))
serializeValueClientOrigin xsRef topic = do
mState <- getTopicState xsRef topic
case mState of
NoTopic -> pure NoTopic
HasTopic (TestTopicState{serialize,clientG,serverS}) -> fmap HasTopic $ do
mX <- atomically (readTVar clientG)
case mX of
Nothing -> pure NoClientG
Just x -> fmap HasClientG $ do
let val = serialize x
atomically $ writeTVar serverS $ Just val
pure $ Serialized topic val
gotClientSerialize :: TestSuiteState
-> TestTopic
-> Value
-> IO (HasTopic ())
gotClientSerialize xsRef topic value = do
mState <- getTopicState xsRef topic
case mState of
NoTopic -> pure NoTopic
HasTopic (TestTopicState{deserialize,clientS}) -> fmap HasTopic $ do
atomically $ writeTVar clientS $ Just value
pure ()
deserializeValueClientOrigin :: TestSuiteState
-> TestTopic
-> IO (HasTopic (HasClientS (DesValue ChannelMsg)))
deserializeValueClientOrigin xsRef topic = do
mState <- getTopicState xsRef topic
case mState of
NoTopic -> pure NoTopic
HasTopic (TestTopicState{deserialize,clientS,serverD,serialize}) -> fmap HasTopic $ do
mX <- atomically (readTVar clientS)
case mX of
Nothing -> pure NoClientS
Just x -> fmap HasClientS $ case parseEither deserialize x of
Left e -> pure (CantDes e)
Right y -> do
atomically $ writeTVar serverD $ Just y
pure $ DesValue $ DeSerialized topic $ serialize y
gotClientDeSerialize :: TestSuiteState
-> TestTopic
-> Value
-> IO (HasTopic (DesValue ()))
gotClientDeSerialize xsRef topic value = do
mState <- getTopicState xsRef topic
case mState of
NoTopic -> pure NoTopic
HasTopic (TestTopicState{deserialize,clientD}) -> fmap HasTopic $ do
case parseEither deserialize value of
Left e -> pure (CantDes e)
Right y -> do
atomically $ writeTVar clientD $ Just y
pure (DesValue ())
verify :: TestSuiteState
-> TestTopic
-> IO
( HasTopic
( HasServerG
( HasClientS
( ServerSerializedMatch
( HasServerD
( DesValue
( ServerDeSerializedMatch
( HasClientG
( HasServerS
( ClientSerializedMatch
( HasClientD
( DesValue
( ClientDeSerializedMatch ())))))))))))))
verify xsRef topic = do
mState <- getTopicState xsRef topic
case mState of
NoTopic -> pure NoTopic
HasTopic TestTopicState{..} -> fmap HasTopic $ do
mServerG <- atomically (readTVar serverG)
case mServerG of
Nothing -> pure NoServerG
Just serverG' -> fmap HasServerG $ do
mClientS <- atomically (readTVar clientS)
case mClientS of
Nothing -> pure NoClientS
Just clientS' -> fmap HasClientS $ do
if serialize serverG' /= clientS'
then pure ServerSerializedMismatch
else fmap ServerSerializedMatch $ do
mServerD <- atomically (readTVar serverD)
case mServerD of
Nothing -> pure NoServerD
Just serverD' -> fmap HasServerD $ do
case parseEither deserialize clientS' of
Left e -> pure (CantDes e)
Right serverD''
| serverD'' /= serverD' -> pure (DesValue ServerDeSerializedMismatch)
| otherwise -> fmap (DesValue . ServerDeSerializedMatch) $ do
mClientG <- atomically (readTVar clientG)
case mClientG of
Nothing -> pure NoClientG
Just clientG' -> fmap HasClientG $ do
mServerS <- atomically (readTVar serverS)
case mServerS of
Nothing -> pure NoServerS
Just serverS' -> fmap HasServerS $ do
if serialize clientG' /= serverS'
then pure ClientSerializedMismatch
else fmap ClientSerializedMatch $ do
mClientD <- atomically (readTVar clientD)
case mClientD of
Nothing -> pure NoClientD
Just clientD' -> fmap HasClientD $ do
case parseEither deserialize serverS' of
Left e -> pure (CantDes e)
Right serverS''
| serverS'' /= clientD' -> pure (DesValue ClientDeSerializedMismatch)
| otherwise -> do
fmap (DesValue . ClientDeSerializedMatch) $ pure ()