packages feed

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