-- Copyright (C) 2010-2012 John Millikin -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. module DBusTests.Util ( assertVariant , assertValue , assertAtom , assertException , assertThrows , getTempPath , listenRandomUnixPath , listenRandomUnixAbstract , listenRandomIPv4 , listenRandomIPv6 , noIPv6 , forkVar , withEnv , countFileDescriptors , dropWhileEnd , halfSized , clampedSize , smallListOf , smallListOf1 , DBusTests.Util.requireLeft , DBusTests.Util.requireRight ) where import Control.Concurrent import Control.Exception (Exception, IOException, try, bracket, bracket_) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Trans.Resource import Data.Bits ((.&.)) import Data.Char (chr) import System.Directory (getTemporaryDirectory, removeFile) import System.FilePath (()) import Test.QuickCheck hiding ((.&.)) import Test.Tasty.HUnit import qualified Data.ByteString import qualified Data.ByteString.Lazy import qualified Data.Map as Map import qualified Data.Text as T import qualified Network.Socket as NS import qualified System.Posix as Posix import DBus import DBus.Internal.Types assertVariant :: (Eq a, Show a, IsVariant a) => Type -> a -> Test.Tasty.HUnit.Assertion assertVariant t a = do t @=? variantType (toVariant a) Just a @=? fromVariant (toVariant a) toVariant a @=? toVariant a assertValue :: (Eq a, Show a, IsValue a) => Type -> a -> Test.Tasty.HUnit.Assertion assertValue t a = do t @=? DBus.typeOf a t @=? DBus.Internal.Types.typeOf a t @=? valueType (toValue a) fromValue (toValue a) @?= Just a toValue a @=? toValue a assertVariant t a assertAtom :: (Eq a, Show a, IsAtom a) => Type -> a -> Test.Tasty.HUnit.Assertion assertAtom t a = do t @=? (atomType (toAtom a)) fromAtom (toAtom a) @?= (Just a) toAtom a @=? toAtom a assertValue t a getTempPath :: IO String getTempPath = do tmp <- getTemporaryDirectory uuid <- randomUUID return (tmp formatUUID uuid) listenRandomUnixPath :: MonadResource m => m Address listenRandomUnixPath = do path <- liftIO getTempPath let sockAddr = NS.SockAddrUnix path (_, sock) <- allocate (NS.socket NS.AF_UNIX NS.Stream NS.defaultProtocol) NS.close liftIO (NS.bind sock sockAddr) liftIO (NS.listen sock 1) _ <- register (removeFile path) let Just addr = address "unix" (Map.fromList [ ("path", path) ]) return addr listenRandomUnixAbstract :: MonadResource m => m (Address, ReleaseKey) listenRandomUnixAbstract = do uuid <- liftIO randomUUID let sockAddr = NS.SockAddrUnix ('\x00' : formatUUID uuid) (key, sock) <- allocate (NS.socket NS.AF_UNIX NS.Stream NS.defaultProtocol) NS.close liftIO $ NS.bind sock sockAddr liftIO $ NS.listen sock 1 let Just addr = address "unix" (Map.fromList [ ("abstract", formatUUID uuid) ]) return (addr, key) listenRandomIPv4 :: MonadResource m => m (Address, NS.Socket, ReleaseKey) listenRandomIPv4 = do let hints = NS.defaultHints { NS.addrFlags = [NS.AI_NUMERICHOST] , NS.addrFamily = NS.AF_INET , NS.addrSocketType = NS.Stream } hostAddr <- liftIO $ NS.getAddrInfo (Just hints) (Just "127.0.0.1") Nothing let sockAddr = NS.addrAddress $ head hostAddr (key, sock) <- allocate (NS.socket NS.AF_INET NS.Stream NS.defaultProtocol) NS.close liftIO $ NS.bind sock sockAddr liftIO $ NS.listen sock 1 sockPort <- liftIO $ NS.socketPort sock let Just addr = address "tcp" (Map.fromList [ ("family", "ipv4") , ("host", "localhost") , ("port", show (toInteger sockPort)) ]) return (addr, sock, key) listenRandomIPv6 :: MonadResource m => m Address listenRandomIPv6 = do addrs <- liftIO $ NS.getAddrInfo Nothing (Just "::1") Nothing let sockAddr = case addrs of [] -> error "listenRandomIPv6: no address for localhost?" a:_ -> NS.addrAddress a (_, sock) <- allocate (NS.socket NS.AF_INET6 NS.Stream NS.defaultProtocol) NS.close liftIO $ NS.bind sock sockAddr liftIO $ NS.listen sock 1 sockPort <- liftIO $ NS.socketPort sock let Just addr = address "tcp" (Map.fromList [ ("family", "ipv6") , ("host", "::1") , ("port", show (toInteger sockPort)) ]) return addr noIPv6 :: IO Bool noIPv6 = do tried <- try (NS.getAddrInfo Nothing (Just "::1") Nothing) case (tried :: Either IOException [NS.AddrInfo]) of Left _ -> return True Right addrs -> return (null addrs) forkVar :: MonadIO m => IO a -> m (MVar a) forkVar io = liftIO $ do var <- newEmptyMVar _ <- forkIO (io >>= putMVar var) return var withEnv :: MonadIO m => String -> Maybe String -> IO a -> m a withEnv name value io = liftIO $ do let set val = case val of Just x -> Posix.setEnv name x True Nothing -> Posix.unsetEnv name old <- Posix.getEnv name bracket_ (set value) (set old) io countFileDescriptors :: MonadIO m => m Int countFileDescriptors = liftIO io where io = do pid <- Posix.getProcessID let fdDir = "/proc/" ++ show pid ++ "/fd" bracket (Posix.openDirStream fdDir) Posix.closeDirStream countDirEntries countDirEntries dir = loop 0 where loop n = do name <- Posix.readDirStream dir if null name then return n else loop (n + 1) halfSized :: Gen a -> Gen a halfSized gen = sized (\n -> if n > 0 then resize (div n 2) gen else gen) smallListOf :: Gen a -> Gen [a] smallListOf gen = clampedSize 10 (listOf gen) smallListOf1 :: Gen a -> Gen [a] smallListOf1 gen = clampedSize 10 (listOf1 gen) clampedSize :: Int -> Gen a -> Gen a clampedSize maxN gen = sized (\n -> resize (min n maxN) gen) instance Arbitrary T.Text where arbitrary = fmap T.pack genUnicode genUnicode :: Gen [Char] genUnicode = string where string = sized $ \n -> do k <- choose (0,n) sequence [ char | _ <- [1..k] ] excluding :: [a -> Bool] -> Gen a -> Gen a excluding bad gen = loop where loop = do x <- gen if or (map ($ x) bad) then loop else return x reserved = [lowSurrogate, highSurrogate, noncharacter] lowSurrogate c = c >= 0xDC00 && c <= 0xDFFF highSurrogate c = c >= 0xD800 && c <= 0xDBFF noncharacter c = masked == 0xFFFE || masked == 0xFFFF where masked = c .&. 0xFFFF ascii = choose (0x20, 0x7F) plane0 = choose (0xF0, 0xFFFF) plane1 = oneof [ choose (0x10000, 0x10FFF) , choose (0x11000, 0x11FFF) , choose (0x12000, 0x12FFF) , choose (0x13000, 0x13FFF) , choose (0x1D000, 0x1DFFF) , choose (0x1F000, 0x1FFFF) ] plane2 = oneof [ choose (0x20000, 0x20FFF) , choose (0x21000, 0x21FFF) , choose (0x22000, 0x22FFF) , choose (0x23000, 0x23FFF) , choose (0x24000, 0x24FFF) , choose (0x25000, 0x25FFF) , choose (0x26000, 0x26FFF) , choose (0x27000, 0x27FFF) , choose (0x28000, 0x28FFF) , choose (0x29000, 0x29FFF) , choose (0x2A000, 0x2AFFF) , choose (0x2B000, 0x2BFFF) , choose (0x2F000, 0x2FFFF) ] plane14 = choose (0xE0000, 0xE0FFF) planes = [ascii, plane0, plane1, plane2, plane14] char = chr `fmap` excluding reserved (oneof planes) instance Arbitrary Data.ByteString.ByteString where arbitrary = fmap Data.ByteString.pack arbitrary instance Arbitrary Data.ByteString.Lazy.ByteString where arbitrary = fmap Data.ByteString.Lazy.fromChunks arbitrary dropWhileEnd :: (Char -> Bool) -> String -> String dropWhileEnd p = T.unpack . T.dropWhileEnd p . T.pack requireLeft :: Show b => Either a b -> IO a requireLeft (Left a) = return a requireLeft (Right b) = assertFailure ("Right " ++ show b ++ " is not Left") >> undefined requireRight :: Show a => Either a b -> IO b requireRight (Right b) = return b requireRight (Left a) = assertFailure ("Left " ++ show a ++ " is not Right") >> undefined assertException :: (Eq e, Exception e) => e -> IO a -> Test.Tasty.HUnit.Assertion assertException e f = do result <- try f case result of Left ex -> ex @?= e Right _ -> assertFailure "expected exception not thrown" assertThrows :: Exception e => (e -> Bool) -> IO a -> Test.Tasty.HUnit.Assertion assertThrows check f = do result <- try f case result of Left ex -> assertBool ("unexpected exception " ++ show ex) (check ex) Right _ -> assertFailure "expected exception not thrown"