project-m36-0.1: test/Server/Main.hs
{-# LANGUAGE CPP #-}
{-
test client/server interaction
-}
import Test.HUnit
import ProjectM36.Client
import qualified ProjectM36.Client as C
import ProjectM36.Server.EntryPoints
import ProjectM36.Server
import ProjectM36.Server.Config
import ProjectM36.Relation
import ProjectM36.TupleSet
import ProjectM36.IsomorphicSchema
import ProjectM36.Base
import System.Exit
import Control.Concurrent
import Network.Transport (EndPointAddress)
import Network.Transport.TCP (encodeEndPointAddress, decodeEndPointAddress)
import Data.Either (isRight)
import Data.Maybe (isJust)
import Control.Exception
--import Control.Monad.IO.Class
#if defined(linux_HOST_OS)
import System.Directory
#endif
testList :: SessionId -> Connection -> MVar () -> Test
testList sessionId conn notificationTestMVar = TestList $ serverTests ++ sessionTests
where
sessionTests = map (\t -> t sessionId conn) [
testRelationalExpr,
testSchemaExpr,
testTypeForRelationalExpr,
testDatabaseContextExpr,
testGraphExpr,
testPlanForDatabaseContextExpr,
testTransactionGraphAsRelation,
testHeadTransactionId,
testHeadName,
testSession,
testRelationVariableSummary,
testNotification notificationTestMVar
]
serverTests = [testRequestTimeout, testFileDescriptorCount]
main :: IO ()
main = do
(serverAddress, _) <- launchTestServer 0
notificationTestMVar <- newEmptyMVar
eTestConn <- testConnection serverAddress notificationTestMVar
case eTestConn of
Left err -> putStrLn (show err) >> exitFailure
Right (session, testConn) -> do
tcounts <- runTestTT (testList session testConn notificationTestMVar)
if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
{-main = do
tcounts <- runTestTT (TestList [testRequestTimeout])
if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess-}
testDatabaseName :: DatabaseName
testDatabaseName = "test"
testConnection :: EndPointAddress -> MVar () -> IO (Either ConnectionError (SessionId, Connection))
testConnection serverAddress mvar = do
let Just (host, service, _) = decodeEndPointAddress serverAddress
serverAddress' = encodeEndPointAddress host service 1
let connInfo = RemoteProcessConnectionInfo testDatabaseName (NodeId serverAddress') (testNotificationCallback mvar)
--putStrLn ("testConnection: " ++ show serverAddress)
eConn <- connectProjectM36 connInfo
case eConn of
Left err -> pure $ Left err
Right conn -> do
eSessionId <- createSessionAtHead defaultHeadName conn
case eSessionId of
Left _ -> error "failed to create session"
Right sessionId -> pure $ Right (sessionId, conn)
-- | A version of 'launchServer' which returns the port on which the server is listening on a secondary thread
launchTestServer :: Timeout -> IO (EndPointAddress, ThreadId)
launchTestServer ti = do
let config = defaultServerConfig { databaseName = testDatabaseName,
perRequestTimeout = ti,
testMode = True,
bindPort = 0
}
addressMVar <- newEmptyMVar
tid <- forkIO $ launchServer config (Just addressMVar) >> pure ()
endPointAddress <- takeMVar addressMVar
--liftIO $ putStrLn ("launched server on " ++ show endPointAddress)
pure (endPointAddress, tid)
testRelationalExpr :: SessionId -> Connection -> Test
testRelationalExpr sessionId conn = TestCase $ do
relResult <- executeRelationalExpr sessionId conn (RelationVariable "true" ())
assertEqual "invalid relation result" (Right relationTrue) relResult
-- test adding an removing a schema against true/false relations
testSchemaExpr :: SessionId -> Connection -> Test
testSchemaExpr sessionId conn = TestCase $ do
result <- executeSchemaExpr sessionId conn (AddSubschema "test-schema" [IsoRename "table_dee" "true", IsoRename "table_dum" "false"])
assertEqual "executeSchemaExpr" Nothing result
result' <- executeSchemaExpr sessionId conn (RemoveSubschema "test-schema")
assertEqual "executeSchemaExpr2" Nothing result'
testDatabaseContextExpr :: SessionId -> Connection -> Test
testDatabaseContextExpr sessionId conn = TestCase $ do
let attrExprs = [AttributeAndTypeNameExpr "x" (PrimitiveTypeConstructor "Text" TextAtomType) ()]
attrs = attributesFromList [Attribute "x" TextAtomType]
testrv = "testrv"
dbResult <- executeDatabaseContextExpr sessionId conn (Define testrv attrExprs)
case dbResult of
Just err -> assertFailure (show err)
Nothing -> do
eRel <- executeRelationalExpr sessionId conn (RelationVariable testrv ())
let expected = mkRelation attrs emptyTupleSet
case eRel of
Left err -> assertFailure (show err)
Right rel -> assertEqual "dbcontext definition failed" expected (Right rel)
testGraphExpr :: SessionId -> Connection -> Test
testGraphExpr sessionId conn = TestCase $ do
graphResult <- executeGraphExpr sessionId conn (JumpToHead "master")
case graphResult of
Just err -> assertFailure (show err)
Nothing -> pure ()
testTypeForRelationalExpr :: SessionId -> Connection -> Test
testTypeForRelationalExpr sessionId conn = TestCase $ do
relResult <- typeForRelationalExpr sessionId conn (RelationVariable "true" ())
case relResult of
Left err -> assertFailure (show err)
Right rel -> assertEqual "typeForRelationalExpr failure" relationFalse rel
testPlanForDatabaseContextExpr :: SessionId -> Connection -> Test
testPlanForDatabaseContextExpr sessionId conn = TestCase $ do
let attrExprs = [AttributeAndTypeNameExpr "x" (PrimitiveTypeConstructor "Int" IntAtomType) ()]
testrv = "testrv"
dbExpr = Define testrv attrExprs
planResult <- planForDatabaseContextExpr sessionId conn dbExpr
case planResult of
Left err -> assertFailure (show err)
Right plan -> assertEqual "planForDatabaseContextExpr failure" dbExpr plan
testTransactionGraphAsRelation :: SessionId -> Connection -> Test
testTransactionGraphAsRelation sessionId conn = TestCase $ do
eGraph <- transactionGraphAsRelation sessionId conn
case eGraph of
Left err -> assertFailure (show err)
Right _ -> pure ()
testHeadTransactionId :: SessionId -> Connection -> Test
testHeadTransactionId sessionId conn = TestCase $ do
uuid <- headTransactionId sessionId conn
assertBool "invalid head transaction uuid" (isJust uuid)
pure ()
testHeadName :: SessionId -> Connection -> Test
testHeadName sessionId conn = TestCase $ do
mHeadName <- headName sessionId conn
assertEqual "headName failure" (Just "master") mHeadName
testRelationVariableSummary :: SessionId -> Connection -> Test
testRelationVariableSummary sessionId conn = TestCase $ do
eRel <- C.relationVariablesAsRelation sessionId conn
case eRel of
Left err -> assertFailure ("relvar summary failed " ++ show err)
Right rel -> assertBool "invalid tuple count in relvar summary" (cardinality rel == Finite 2)
testSession :: SessionId -> Connection -> Test
testSession _ conn = TestCase $ do
-- create and close a new session using AtHead and AtCommit
eSessionId1 <- createSessionAtHead defaultHeadName conn
case eSessionId1 of
Left _ -> assertFailure "invalid session"
Right sessionId1 -> do
mHeadId <- headTransactionId sessionId1 conn
case mHeadId of
Nothing -> assertFailure "invalid head id"
Just headId -> do
eSessionId2 <- createSessionAtCommit headId conn
assertBool ("invalid session: " ++ show eSessionId2) (isRight eSessionId2)
closeSession sessionId1 conn
testNotificationCallback :: MVar () -> NotificationCallback
testNotificationCallback mvar _ _ = putMVar mvar ()
-- create a relvar x, add a notification on x, update x and wait for the notification
testNotification :: MVar () -> SessionId -> Connection -> Test
testNotification mvar sess conn = TestCase $ do
let relvarx = RelationVariable "x" ()
check x = x >>= maybe (pure ()) (\err -> assertFailure (show err))
check $ executeDatabaseContextExpr sess conn (Assign "x" (ExistingRelation relationTrue))
check $ executeDatabaseContextExpr sess conn (AddNotification "test notification" relvarx relvarx)
check $ commit sess conn ForbidEmptyCommitOption
check $ executeDatabaseContextExpr sess conn (Assign "x" (ExistingRelation relationFalse))
check $ commit sess conn ForbidEmptyCommitOption
takeMVar mvar
testRequestTimeout :: Test
testRequestTimeout = TestCase $ do
(serverAddress, serverTid) <- launchTestServer 1000
unusedMVar <- newEmptyMVar
eTestConn <- testConnection serverAddress unusedMVar
--eTestConn <- testConnection (encodeEndPointAddress "127.0.0.1" "10000" 1) unusedMVar
case eTestConn of
Left err -> putStrLn ("failed to connect: " ++ show err) >> exitFailure
Right (session, testConn) -> do
res <- catchJust (\exc -> if exc == RequestTimeoutException then Just exc else Nothing) (callTestTimeout_ session testConn) (const (pure False))
assertBool "exception was not thrown" (not res)
killThread serverTid
testFileDescriptorCount :: Test
#if defined(linux_HOST_OS)
--validate that creating a server, connecting a client, and then disconnecting doesn't leak file descriptors
testFileDescriptorCount = TestCase $ do
(serverAddress, serverTid) <- launchTestServer 1000
startCount <- fdCount
unusedMVar <- newEmptyMVar
Right (_, testConn) <- testConnection serverAddress unusedMVar
close testConn
endCount <- fdCount
assertEqual "fd leak" startCount endCount
killThread serverTid
-- returns the number of open file descriptors -- linux only /proc usage
fdCount :: IO Int
fdCount = do
fds <- getDirectoryContents "/proc/self/fd"
pure ((length fds) - 2)
#else
--pass on non-linux platforms
testFileDescriptorCount = TestCase (pure ())
#endif