{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} -- Copyright (C) 2012 John Millikin -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . module DBusTests.Transport (test_Transport) where import Test.Chell import Control.Concurrent import Control.Monad.IO.Class (MonadIO, liftIO) import qualified Data.ByteString import Data.Function (fix) import Data.List (isPrefixOf) import qualified Data.Map as Map import qualified Network as N import qualified Network.Socket as NS import Network.Socket.ByteString (sendAll, recv) import System.Directory (getTemporaryDirectory, removeFile) import DBus import DBus.Transport import DBusTests.Util test_Transport :: Suite test_Transport = suite "Transport" $ suiteTests suite_TransportOpen ++ suiteTests suite_TransportListen ++ suiteTests suite_TransportAccept ++ [ test_TransportSendReceive , test_HandleLostConnection ] suite_TransportOpen :: Suite suite_TransportOpen = suite "transportOpen" $ [ test_OpenUnknown ] ++ suiteTests suite_OpenUnix ++ suiteTests suite_OpenTcp suite_TransportListen :: Suite suite_TransportListen = suite "transportListen" $ [ test_ListenUnknown ] ++ suiteTests suite_ListenUnix ++ suiteTests suite_ListenTcp suite_TransportAccept :: Suite suite_TransportAccept = suite "transportAccept" [ test_AcceptSocket , test_AcceptSocketClosed ] test_OpenUnknown :: Test test_OpenUnknown = assertions "unknown" $ do let Just addr = address "noexist" Map.empty $assert $ throwsEq ((transportError "Unknown address method: \"noexist\"") { transportErrorAddress = Just addr }) (transportOpen socketTransportOptions addr) suite_OpenUnix :: Suite suite_OpenUnix = suite "unix" [ test_OpenUnix_Path , test_OpenUnix_Abstract , test_OpenUnix_TooFew , test_OpenUnix_TooMany , test_OpenUnix_NotListening ] test_OpenUnix_Path :: Test test_OpenUnix_Path = assertions "path" $ do (addr, networkSocket) <- listenRandomUnixPath afterTest (N.sClose networkSocket) fdcountBefore <- countFileDescriptors t <- liftIO (transportOpen socketTransportOptions addr) liftIO (transportClose t) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenUnix_Abstract :: Test test_OpenUnix_Abstract = assertions "abstract" $ do (addr, networkSocket) <- listenRandomUnixAbstract afterTest (N.sClose networkSocket) fdcountBefore <- countFileDescriptors t <- liftIO (transportOpen socketTransportOptions addr) liftIO (transportClose t) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenUnix_TooFew :: Test test_OpenUnix_TooFew = assertions "too-few" $ do fdcountBefore <- countFileDescriptors let Just addr = address "unix" Map.empty $assert $ throwsEq ((transportError "One of 'path' or 'abstract' must be specified for the 'unix' transport.") { transportErrorAddress = Just addr }) (transportOpen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenUnix_TooMany :: Test test_OpenUnix_TooMany = assertions "too-many" $ do fdcountBefore <- countFileDescriptors let Just addr = address "unix" (Map.fromList [ ("path", "foo") , ("abstract", "bar") ]) $assert $ throwsEq ((transportError "Only one of 'path' or 'abstract' may be specified for the 'unix' transport.") { transportErrorAddress = Just addr }) (transportOpen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenUnix_NotListening :: Test test_OpenUnix_NotListening = assertions "not-listening" $ do fdcountBefore <- countFileDescriptors (addr, networkSocket) <- listenRandomUnixAbstract liftIO (NS.sClose networkSocket) $assert $ throwsEq ((transportError "connect: does not exist (Connection refused)") { transportErrorAddress = Just addr }) (transportOpen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) suite_OpenTcp :: Suite suite_OpenTcp = suite "tcp" [ test_OpenTcp_IPv4 , skipWhen noIPv6 test_OpenTcp_IPv6 , test_OpenTcp_Unknown , test_OpenTcp_NoPort , test_OpenTcp_InvalidPort , test_OpenTcp_NoUsableAddresses , test_OpenTcp_NotListening ] test_OpenTcp_IPv4 :: Test test_OpenTcp_IPv4 = assertions "ipv4" $ do (addr, networkSocket) <- listenRandomIPv4 afterTest (N.sClose networkSocket) fdcountBefore <- countFileDescriptors t <- liftIO (transportOpen socketTransportOptions addr) liftIO (transportClose t) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenTcp_IPv6 :: Test test_OpenTcp_IPv6 = assertions "ipv6" $ do (addr, networkSocket) <- listenRandomIPv6 afterTest (N.sClose networkSocket) fdcountBefore <- countFileDescriptors t <- liftIO (transportOpen socketTransportOptions addr) liftIO (transportClose t) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenTcp_Unknown :: Test test_OpenTcp_Unknown = assertions "unknown-family" $ do fdcountBefore <- countFileDescriptors let Just addr = address "tcp" (Map.fromList [ ("family", "noexist") , ("port", "1234") ]) $assert $ throwsEq ((transportError "Unknown socket family for TCP transport: \"noexist\"") { transportErrorAddress = Just addr }) (transportOpen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenTcp_NoPort :: Test test_OpenTcp_NoPort = assertions "no-port" $ do fdcountBefore <- countFileDescriptors let Just addr = address "tcp" (Map.fromList [ ("family", "ipv4") ]) $assert $ throwsEq ((transportError "TCP transport requires the `port' parameter.") { transportErrorAddress = Just addr }) (transportOpen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenTcp_InvalidPort :: Test test_OpenTcp_InvalidPort = assertions "invalid-port" $ do fdcountBefore <- countFileDescriptors let Just addr = address "tcp" (Map.fromList [ ("family", "ipv4") , ("port", "123456") ]) $assert $ throwsEq ((transportError "Invalid socket port for TCP transport: \"123456\"") { transportErrorAddress = Just addr }) (transportOpen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenTcp_NoUsableAddresses :: Test test_OpenTcp_NoUsableAddresses = assertions "no-usable-addresses" $ do fdcountBefore <- countFileDescriptors let Just addr = address "tcp" (Map.fromList [ ("family", "ipv4") , ("port", "1234") , ("host", "256.256.256.256") ]) $assert $ throws (\err -> and [ "getAddrInfo: does not exist" `isPrefixOf` transportErrorMessage err , transportErrorAddress err == Just addr ]) (transportOpen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_OpenTcp_NotListening :: Test test_OpenTcp_NotListening = assertions "too-many" $ do fdcountBefore <- countFileDescriptors (addr, networkSocket) <- listenRandomIPv4 liftIO (NS.sClose networkSocket) $assert $ throwsEq ((transportError "connect: does not exist (Connection refused)") { transportErrorAddress = Just addr }) (transportOpen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_TransportSendReceive :: Test test_TransportSendReceive = assertions "send-receive" $ do (addr, networkSocket) <- listenRandomIPv4 afterTest (N.sClose networkSocket) -- a simple echo server, which sends back anything it receives. _ <- liftIO $ forkIO $ do (s, _) <- NS.accept networkSocket fix $ \loop -> do bytes <- recv s 50 if Data.ByteString.null bytes then NS.sClose s else do sendAll s bytes loop t <- liftIO (transportOpen socketTransportOptions addr) afterTest (transportClose t) -- small chunks of data are combined do var <- forkVar (transportGet t 3) liftIO (transportPut t "1") liftIO (transportPut t "2") liftIO (transportPut t "3") bytes <- liftIO (readMVar var) $assert (equal bytes "123") -- large chunks of data are read in full do let sentBytes = Data.ByteString.replicate (4096 * 100) 0 var <- forkVar (transportGet t (4096 * 100)) liftIO (transportPut t sentBytes) bytes <- liftIO (readMVar var) $assert (equal bytes sentBytes) test_HandleLostConnection :: Test test_HandleLostConnection = assertions "handle-lost-connection" $ do (addr, networkSocket) <- listenRandomIPv4 afterTest (N.sClose networkSocket) _ <- liftIO $ forkIO $ do (s, _) <- NS.accept networkSocket sendAll s "123" NS.sClose s t <- liftIO (transportOpen socketTransportOptions addr) afterTest (transportClose t) bytes <- liftIO (transportGet t 4) $assert (equal bytes "123") test_ListenUnknown :: Test test_ListenUnknown = assertions "unknown" $ do let Just addr = address "noexist" Map.empty $assert $ throwsEq ((transportError "Unknown address method: \"noexist\"") { transportErrorAddress = Just addr }) (transportListen socketTransportOptions addr) suite_ListenUnix :: Suite suite_ListenUnix = suite "unix" [ test_ListenUnix_Path , test_ListenUnix_Abstract , test_ListenUnix_Tmpdir , test_ListenUnix_TooFew , test_ListenUnix_TooMany , test_ListenUnix_InvalidBind ] test_ListenUnix_Path :: Test test_ListenUnix_Path = assertions "path" $ do path <- liftIO getTempPath let Just addr = address "unix" (Map.fromList [ ("path", path) ]) l <- liftIO (transportListen socketTransportOptions addr) afterTest (transportListenerClose l) afterTest (removeFile path) -- listener address is random, so it can't be checked directly. let addrParams = addressParameters (transportListenerAddress l) $expect (sameItems (Map.keys addrParams) ["path", "guid"]) $expect (equal (Map.lookup "path" addrParams) (Just path)) test_ListenUnix_Abstract :: Test test_ListenUnix_Abstract = assertions "abstract" $ do path <- liftIO getTempPath let Just addr = address "unix" (Map.fromList [ ("abstract", path) ]) l <- liftIO (transportListen socketTransportOptions addr) afterTest (transportListenerClose l) -- listener address is random, so it can't be checked directly. let addrParams = addressParameters (transportListenerAddress l) $expect (sameItems (Map.keys addrParams) ["abstract", "guid"]) $expect (equal (Map.lookup "abstract" addrParams) (Just path)) test_ListenUnix_Tmpdir :: Test test_ListenUnix_Tmpdir = assertions "tmpdir" $ do tmpdir <- liftIO getTemporaryDirectory let Just addr = address "unix" (Map.fromList [ ("tmpdir", tmpdir) ]) l <- liftIO (transportListen socketTransportOptions addr) afterTest (transportListenerClose l) -- listener address is random, so it can't be checked directly. let addrKeys = Map.keys (addressParameters (transportListenerAddress l)) $expect ("path" `elem` addrKeys || "abstract" `elem` addrKeys) test_ListenUnix_TooFew :: Test test_ListenUnix_TooFew = assertions "too-few" $ do let Just addr = address "unix" Map.empty $assert $ throwsEq ((transportError "One of 'abstract', 'path', or 'tmpdir' must be specified for the 'unix' transport.") { transportErrorAddress = Just addr }) (transportListen socketTransportOptions addr) test_ListenUnix_TooMany :: Test test_ListenUnix_TooMany = assertions "too-many" $ do let Just addr = address "unix" (Map.fromList [ ("path", "foo") , ("abstract", "bar") ]) $assert $ throwsEq ((transportError "Only one of 'abstract', 'path', or 'tmpdir' may be specified for the 'unix' transport.") { transportErrorAddress = Just addr }) (transportListen socketTransportOptions addr) test_ListenUnix_InvalidBind :: Test test_ListenUnix_InvalidBind = assertions "invalid-bind" $ do fdcountBefore <- countFileDescriptors let Just addr = address "unix" (Map.fromList [ ("path", "/") ]) $assert $ throwsEq ((transportError "bind: resource busy (Address already in use)") { transportErrorAddress = Just addr }) (transportListen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) suite_ListenTcp :: Suite suite_ListenTcp = suite "tcp" [ test_ListenTcp_IPv4 , skipWhen noIPv6 test_ListenTcp_IPv6 , test_ListenTcp_Unknown , test_ListenTcp_InvalidPort , test_ListenTcp_InvalidBind ] test_ListenTcp_IPv4 :: Test test_ListenTcp_IPv4 = assertions "ipv4" $ do let Just addr = address "tcp" (Map.fromList [ ("family", "ipv4") ]) l <- liftIO (transportListen socketTransportOptions addr) afterTest (transportListenerClose l) let params = addressParameters (transportListenerAddress l) $expect (equal (Map.lookup "family" params) (Just "ipv4")) $expect ("port" `elem` Map.keys params) test_ListenTcp_IPv6 :: Test test_ListenTcp_IPv6 = assertions "ipv6" $ do let Just addr = address "tcp" (Map.fromList [ ("family", "ipv6") ]) l <- liftIO (transportListen socketTransportOptions addr) afterTest (transportListenerClose l) let params = addressParameters (transportListenerAddress l) $expect (equal (Map.lookup "family" params) (Just "ipv6")) $expect ("port" `elem` Map.keys params) test_ListenTcp_Unknown :: Test test_ListenTcp_Unknown = assertions "unknown-family" $ do let Just addr = address "tcp" (Map.fromList [ ("family", "noexist") , ("port", "1234") ]) $assert $ throwsEq ((transportError "Unknown socket family for TCP transport: \"noexist\"") { transportErrorAddress = Just addr }) (transportListen socketTransportOptions addr) test_ListenTcp_InvalidPort :: Test test_ListenTcp_InvalidPort = assertions "invalid-port" $ do let Just addr = address "tcp" (Map.fromList [ ("family", "ipv4") , ("port", "123456") ]) $assert $ throwsEq ((transportError "Invalid socket port for TCP transport: \"123456\"") { transportErrorAddress = Just addr }) (transportListen socketTransportOptions addr) test_ListenTcp_InvalidBind :: Test test_ListenTcp_InvalidBind = assertions "invalid-bind" $ do fdcountBefore <- countFileDescriptors let Just addr = address "tcp" (Map.fromList [ ("family", "ipv4") , ("port", "1") ]) $assert $ throwsEq ((transportError "bind: permission denied (Permission denied)") { transportErrorAddress = Just addr }) (transportListen socketTransportOptions addr) fdcountAfter <- countFileDescriptors $assert (equal fdcountBefore fdcountAfter) test_AcceptSocket :: Test test_AcceptSocket = assertions "socket" $ do path <- liftIO getTempPath let Just addr = address "unix" (Map.fromList [ ("abstract", path) ]) listener <- liftIO (transportListen socketTransportOptions addr) afterTest (transportListenerClose listener) acceptedVar <- forkVar (transportAccept listener) openedVar <- forkVar (transportOpen socketTransportOptions addr) accepted <- liftIO (readMVar acceptedVar) opened <- liftIO (readMVar openedVar) afterTest (transportClose accepted) afterTest (transportClose opened) liftIO (transportPut opened "testing") bytes <- liftIO (transportGet accepted 7) $expect (equal bytes "testing") test_AcceptSocketClosed :: Test test_AcceptSocketClosed = assertions "socket-closed" $ do path <- liftIO getTempPath let Just addr = address "unix" (Map.fromList [ ("abstract", path) ]) listener <- liftIO (transportListen socketTransportOptions addr) let listeningAddr = transportListenerAddress listener liftIO (transportListenerClose listener) $assert $ throwsEq ((transportError "user error (accept: can't perform accept on socket ((AF_UNIX,Stream,0)) in status Closed)") { transportErrorAddress = Just listeningAddr }) (transportAccept listener) socketTransportOptions :: TransportOptions SocketTransport socketTransportOptions = transportDefaultOptions