File: Tests.hs

package info (click to toggle)
haskell-snap-server 1.1.2.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, trixie
  • size: 536 kB
  • sloc: haskell: 5,445; ansic: 4; makefile: 2
file content (171 lines) | stat: -rw-r--r-- 6,866 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
{-# LANGUAGE CPP                 #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Snap.Internal.Http.Server.Socket.Tests (tests) where

------------------------------------------------------------------------------
import           Control.Applicative               ((<$>))
import qualified Network.Socket                    as N
------------------------------------------------------------------------------
import           Control.Concurrent                (forkIO, killThread, newEmptyMVar, putMVar, readMVar, takeMVar)
import qualified Control.Exception                 as E
import           Data.IORef                        (newIORef, readIORef, writeIORef)
import           Test.Framework                    (Test)
import           Test.Framework.Providers.HUnit    (testCase)
import           Test.HUnit                        (assertEqual)
------------------------------------------------------------------------------
import qualified Snap.Internal.Http.Server.Socket  as Sock
import           Snap.Test.Common                  (eatException, expectException, withSock)
------------------------------------------------------------------------------
#ifdef HAS_UNIX_SOCKETS
import           System.Directory                  (getTemporaryDirectory)
import           System.FilePath                   ((</>))
import qualified System.Posix                      as Posix
# if !MIN_VERSION_unix(2,6,0)
import           Control.Monad.State               (replicateM)
import           Control.Monad.Trans.State.Strict  as State
import qualified Data.Vector.Unboxed               as V
import           System.Directory                  (createDirectoryIfMissing)
import           System.Random                     (StdGen, newStdGen, randomR)
# endif
#else
import           Snap.Internal.Http.Server.Address (AddressNotSupportedException)
#endif

------------------------------------------------------------------------------
#ifdef HAS_UNIX_SOCKETS
mkdtemp :: String -> IO FilePath
# if MIN_VERSION_unix(2,6,0)
mkdtemp = Posix.mkdtemp

# else

tMPCHARS :: V.Vector Char
tMPCHARS = V.fromList $! ['a'..'z'] ++ ['0'..'9']

mkdtemp template = do
    suffix <- newStdGen >>= return . State.evalState (chooseN 8 tMPCHARS)
    let dir = template ++ suffix
    createDirectoryIfMissing False dir
    return dir
  where
    choose :: V.Vector Char -> State.State StdGen Char
    choose v = do let sz = V.length v
                  idx <- State.state $ randomR (0, sz - 1)
                  return $! (V.!) v idx

    chooseN :: Int -> V.Vector Char -> State.State StdGen String
    chooseN n v = replicateM n $ choose v
#endif
#endif

------------------------------------------------------------------------------
tests :: [Test]
tests = [
          testUnixSocketBind
#if !MIN_VERSION_network(3,0,0)
        , testAcceptFailure
        , testSockClosedOnListenException
#endif
        ]

------------------------------------------------------------------------------
-- TODO: fix these tests which rely on deprecated socket apis
#if !MIN_VERSION_network(3,0,0)
testSockClosedOnListenException :: Test
testSockClosedOnListenException = testCase "socket/closedOnListenException" $ do
    ref <- newIORef Nothing
    expectException $ Sock.bindSocketImpl (sso ref) bs ls "127.0.0.1" 4444
    (Just sock) <- readIORef ref
    let (N.MkSocket _ _ _ _ mvar) = sock
    readMVar mvar >>= assertEqual "socket closed" N.Closed

  where
    sso ref sock _ _ = do
        let (N.MkSocket _ _ _ _ mvar) = sock
        readMVar mvar >>= assertEqual "socket not connected" N.NotConnected
        writeIORef ref (Just sock) >> fail "set socket option"
    bs _ _ = fail "bindsocket"
    ls _ _ = fail "listen"

------------------------------------------------------------------------------
testAcceptFailure :: Test
testAcceptFailure = testCase "socket/acceptAndInitialize" $ do
    sockmvar <- newEmptyMVar
    donemvar <- newEmptyMVar
    E.bracket (Sock.bindSocket "127.0.0.1" $ fromIntegral N.aNY_PORT)
              (N.close)
              (\s -> do
                   p <- fromIntegral <$> N.socketPort s
                   forkIO $ server s sockmvar donemvar
                   E.bracket (forkIO $ client p)
                             (killThread)
                             (\_ -> do
                                csock <- takeMVar sockmvar
                                takeMVar donemvar
                                N.isConnected csock >>=
                                    assertEqual "closed" False
                             )
              )
  where
    server sock sockmvar donemvar = serve `E.finally` putMVar donemvar ()
      where
        serve = eatException $ E.mask $ \restore ->
                Sock.acceptAndInitialize sock restore $ \(csock, _) -> do
                  putMVar sockmvar csock
                  fail "error"

    client port = withSock port (const $ return ())
#endif

testUnixSocketBind :: Test
#ifdef HAS_UNIX_SOCKETS
testUnixSocketBind = testCase "socket/unixSocketBind" $
  withSocketPath $ \path ->  do
#if !MIN_VERSION_network(3,0,0)
    E.bracket (Sock.bindUnixSocket Nothing path) N.close $ \sock -> do
        N.isListening sock >>= assertEqual "listening" True
#endif

    expectException $ E.bracket (Sock.bindUnixSocket Nothing "a/relative/path")
                    N.close doNothing

    expectException $ E.bracket (Sock.bindUnixSocket Nothing "/relative/../path")
                    N.close doNothing

    expectException $ E.bracket (Sock.bindUnixSocket Nothing "/hopefully/not/existing/path")
                    N.close doNothing

#ifdef LINUX
    -- Most (all?) BSD systems ignore access mode on unix sockets.
    -- Should we still check it?

    -- This is pretty much for 100% coverage
    expectException $ E.bracket (Sock.bindUnixSocket Nothing "/")
                    N.close doNothing

    let mode = 0o766
    E.bracket (Sock.bindUnixSocket (Just mode) path) N.close $ \_ -> do
        -- Should check sockFd instead of path?
        sockMode <- fmap Posix.fileMode $ Posix.getFileStatus path
        assertEqual "access mode" (fromIntegral mode) $
            Posix.intersectFileModes Posix.accessModes sockMode
#endif
  where
    doNothing _ = return ()
    withSocketPath act = do
      tmpRoot <- getTemporaryDirectory
      tmpDir <- mkdtemp $ tmpRoot </> "snap-server-test-"
      let path = tmpDir </> "unixSocketBind.sock"
      E.finally (act path) $ do
          eatException $ Posix.removeLink path
          eatException $ Posix.removeDirectory tmpDir

#else
testUnixSocketBind = testCase "socket/unixSocketBind" $ do
    caught <- E.catch (Sock.bindUnixSocket Nothing "/tmp/snap-sock.sock" >> return False)
              $ \(e :: AddressNotSupportedException) -> length (show e) `seq` return True
    assertEqual "not supported" True caught

#endif