haskell-debugger-0.13.0.0: test/haskell/Test/DAP/Messages.hs
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Test.DAP.Messages where
----------------------------------------------------------------------------
import Control.Concurrent.Async
import Control.Monad.IO.Class
import Data.Aeson
import Data.Aeson.Types
import Control.Monad.Reader
import qualified Data.ByteString as BS
import System.IO
import Data.IORef
----------------------------------------------------------------------------
import DAP.Utils
import Control.Concurrent.STM
import qualified Data.Text as T
import Test.DAP.Messages.Parser
----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- * Monad for DAP client context
----------------------------------------------------------------------------
data TestDAPClientContext = TestDAPClientContext
{ clientHandle :: Handle
-- ^ Connection to server
, clientNextSeqRef :: IORef Int
-- ^ Counter for seq numbers
, clientResponses :: TChan Value
-- ^ Collect response messages sent by server
, clientEvents :: TChan Value
-- ^ Collect event messages sent by server
, clientReverseRequests :: TChan Value
-- ^ Collect reverse requests messages sent by server
, clientFullOutput :: TVar [T.Text]
-- ^ The full output is available here in reverse order (from most recent to oldest output strings).
--
-- The output events are STILL available from the events channel (this
-- might be useful if you want to check a certain output event happens
-- after some other specific event like a stopped one, rather than just
-- overall).
--
-- We keep this full text because it is often useful to query the full
-- output and not care about ordering.
, clientSupportsRunInTerminal :: Bool
-- ^ Run test with runInTerminal support?
, clientHandleNoSuccess :: String -> Value -> IO (Maybe Value)
-- ^ How to handle a response with success: false? If this function returns
-- @Just val@ something then execution will resume with the returned @val@
-- rather than aborting.
}
newtype TestDAP a = TestDAP { runTestDAP :: TestDAPClientContext -> IO a }
deriving (Functor, Applicative, Monad, MonadIO, MonadFail, MonadReader TestDAPClientContext) via (ReaderT TestDAPClientContext IO)
--------------------------------------------------------------------------------
-- * Message primitives
--------------------------------------------------------------------------------
type AsyncCont b a = Async b -> TestDAP a
type ResponseCont b a = Async (Response b) -> TestDAP a
-- | Run an action with an Async in the continuation synchronously by simply
-- waiting for the response.
sync :: (ResponseCont b (Response b) -> TestDAP (Response b)) -> TestDAP (Response b)
sync k = k (liftIO . wait)
-- | Send message with next sequence number and expect a response (response
-- value is given as async in continuation)
send :: forall b r. FromJSON b => [Pair] -> ResponseCont b r -> TestDAP r
send message k = do
ctx@TestDAPClientContext{..} <- ask
seqNum <- liftIO $ atomicModifyIORef' clientNextSeqRef (\n -> (n + 1, n))
liftIO $ do
BS.hPutStr clientHandle $
encodeBaseProtocolMessage (object ("seq" .= seqNum : filter ((/= "seq") . fst) message))
withAsync (runTestDAP waitForResponse ctx) $ \v ->
runTestDAP (k $ (\r -> unwrap (fromJSON @(Response b) r) r) <$> v) ctx
where
unwrap (Error e) r = error ("send: Parsing 'Response' failed with " ++ show e ++ " for message: " ++ show r)
unwrap (Success x) _ = x
-- | Reply to reverse request of given seq number
reply :: Int -> [Pair] -> TestDAP ()
reply revReqSeqNum message = do
TestDAPClientContext{..} <- ask
liftIO $ do
BS.hPutStr clientHandle $
encodeBaseProtocolMessage (object ("seq" .= (revReqSeqNum + 1) : filter ((/= "seq") . fst) message))
waitForResponse :: TestDAP Value
waitForResponse = do
TestDAPClientContext{..} <- ask
liftIO $ atomically $ readTChan clientResponses
waitForReverseRequest :: TestDAP Value
waitForReverseRequest = do
TestDAPClientContext{..} <- ask
liftIO $ atomically $ readTChan clientReverseRequests
waitForEvent :: TestDAP Value
waitForEvent = do
TestDAPClientContext{..} <- ask
liftIO $ atomically $ readTChan clientEvents