packages feed

pong-server (empty) → 0.0.1.0

raw patch · 5 files changed

+202/−0 lines, 5 filesdep +QuickCheckdep +basedep +hspecsetup-changed

Dependencies added: QuickCheck, base, hspec, network, pong-server

Files

+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ UNLICENSE view
@@ -0,0 +1,24 @@+This is free and unencumbered software released into the public domain.++Anyone is free to copy, modify, publish, use, compile, sell, or+distribute this software, either in source code form or as a compiled+binary, for any purpose, commercial or non-commercial, and by any+means.++In jurisdictions that recognize copyright laws, the author or authors+of this software dedicate any and all copyright interest in the+software to the public domain. We make this dedication for the benefit+of the public at large and to the detriment of our heirs and+successors. We intend this dedication to be an overt act of+relinquishment in perpetuity of all present and future rights to this+software under copyright law.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.++For more information, please refer to <http://unlicense.org/>
+ pong-server.cabal view
@@ -0,0 +1,47 @@+name:                pong-server+version:             0.0.1.0+synopsis:            A simple embedded pingable server that runs in the background.+description:         Please see README.md+homepage:            http://github.com/RobertFischer/pong-server#readme+license:             PublicDomain+license-file:        UNLICENSE+author:              Robert Fischer+maintainer:          smokejumperit+pong-server@gmail.com+copyright:           2016 Robert Fischer+category:            utilities+build-type:          Simple+-- extra-source-files:+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Axial.Pong+  build-depends:       base >= 4.7 && < 5+                     , network >= 2.6.2.1 && < 2.7+  default-language:    Haskell2010+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+  default-extensions:  OverloadedStrings+                    ,  LambdaCase+                    ,  MultiWayIf+                    ,  TransformListComp+                    ,  FlexibleInstances+                    ,  NoMonomorphismRestriction+                    ,  DeriveGeneric+                    ,  ScopedTypeVariables++test-suite pong-server-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  build-depends:       base+                     , pong-server+                     , hspec >= 2.2.2 && < 3+                     , QuickCheck >= 2.8.1 && < 3+                     , network >= 2.6.2.1 && < 2.7+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables++source-repository head+  type:     git+  location: https://github.com/RobertFischer/pong-server
+ src/Axial/Pong.hs view
@@ -0,0 +1,45 @@+module Axial.Pong+    (+      PongConfig(..)+    , defaultPongConfig+    , withPongServer+    ) where++import Network (withSocketsDo, listenOn, accept, sClose, PortID(..))+import System.IO (hClose, hPutStr, hSetBuffering, BufferMode(..))+import Control.Exception (bracket, finally)+import Control.Concurrent (forkIO, forkFinally, ThreadId, killThread)++data PongConfig = PongConfig {+  pongPort :: Int,+  pongMessage :: () -> IO String+}++newtype PongServer = PongServer ThreadId++defaultPongConfig :: PongConfig+defaultPongConfig = PongConfig 10411 $ const $ pure "pong"++withPongServer :: PongConfig -> IO a -> IO a+withPongServer cfg action = bracket (startServer cfg) stopServer $ const action++startServer :: PongConfig -> IO PongServer+startServer cfg = withSocketsDo $ do+  socket <- listenOn portNum+  threadId <- forkIO $ socketHandler socket+  pure $ PongServer threadId+  where+    socketHandler sock = finally (socketHandlerLoop sock) (sClose sock)+    socketHandlerLoop sock = do+      (handle, _, _) <- accept sock+      _ <- forkFinally (body handle) (const $ hClose handle)+      socketHandlerLoop sock+    body handle = do+      msg <- (pongMessage cfg) ()+      hSetBuffering handle NoBuffering+      hPutStr handle msg+    portNum = PortNumber $ fromIntegral pongPortNum+    pongPortNum = pongPort cfg++stopServer :: PongServer -> IO ()+stopServer (PongServer threadId) = killThread threadId
+ test/Spec.hs view
@@ -0,0 +1,84 @@+import Axial.Pong++import Data.Maybe (isJust, fromJust)++import Test.Hspec+import Test.QuickCheck+import Control.Exception (catch, SomeException)++import Network++import System.Timeout++import GHC.IO.Handle (Handle, hGetLine, hClose)+++main :: IO ()+main = hspec $ do+  describe "Axial Pong server" $ do++    it "cannot be reached when not running" $ do+      reached <- serverReachable ()+      reached `shouldBe` False++    it "can be started and stopped twice" $ do+      reachedBefore <- serverReachable ()+      reachedBefore `shouldBe` False++      withPongServer defaultPongConfig $ do+        reached <- serverReachable ()+        reached `shouldBe` True++      reachedBetween <- serverReachable ()+      reachedBetween `shouldBe` False++      withPongServer defaultPongConfig $ do+        reached <- serverReachable ()+        reached `shouldBe` True++      reachedAfter <- serverReachable ()+      reachedAfter `shouldBe` False++    context "when running" $ around_ (withPongServer defaultPongConfig) $ do+      it "can be reached" $ do+        reached <- serverReachable ()+        reached `shouldBe` True++      it "can be reached twice" $ do+        reached1 <- serverReachable ()+        reached1 `shouldBe` True+        reached2 <- serverReachable ()+        reached2 `shouldBe` True++      it "survives after a client pre-emptively closes the connection" $ do+        handle <- connectServer ()+        hClose handle+        reached <- serverReachable ()+        reached `shouldBe` True++connectServer :: () -> IO Handle+connectServer () = withSocketsDo $ do+  connResult <- timeout (1000 * 1000) $ doConnect+  connResult `shouldSatisfy` isJust+  pure $ fromJust connResult+  where+    doConnect = connectTo serverName portNum+    portNum = PortNumber 10411+    serverName = "localhost"++serverReachable :: () -> IO Bool+serverReachable () = withSocketsDo $ catch tryConnect (\(e::SomeException) -> pure False)+  where+    tryConnect = do+      connResult <- timeout (1000 * 1000) doConnect+      pure $ isJust connResult+    doConnect = do+      socket <- connectTo serverName portNum+      receivedMessage <- hGetLine socket+      pure $ validateMessage receivedMessage+    portNum = PortNumber 10411+    serverName = "localhost"++validateMessage :: String -> Bool+validateMessage = (==) "pong"+