packages feed

haskell-debugger-0.13.0.0: test/haskell/Test/Integration/Exceptions.hs

-- | Exception info tests ported from the NodeJS integration testsuite.
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Test.Integration.Exceptions (exceptionTests) where

import Control.Monad.IO.Class (liftIO)
import qualified Data.Text as T
import Test.DAP
import Test.DAP.Messages.Parser (Event(..))
import Test.Tasty
import Test.Tasty.HUnit
#ifdef mingw32_HOST_OS
import Test.Tasty.ExpectedFailure
#endif
import qualified DAP

exceptionTests :: TestTree
exceptionTests =
#ifdef mingw32_HOST_OS
  ignoreTestBecause "Needs to be fixed for Windows (#199)" $
#endif
  testGroup "DAP.Integration.Exceptions"
    [ testCase "reports nested exceptions and continues after the first break" nestedExceptions
    ]

-- Note: It is not clear that stopping 5 times is the right thing to happen
-- here, but it just tests the existing behaviour.
nestedExceptions :: Assertion
nestedExceptions =
  withTestDAPServer "test/integration/exceptions" [] $ \test_dir server ->
    withTestDAPServerClient server $ do
      let cfg = mkLaunchConfig test_dir "Main.hs"
      _ <- sync $ launchWith cfg
      waitFiltering_ EventTy "initialized"
      setBreakOnException
      _ <- sync configurationDone
      checkNested
      disconnect
  where
    assertExcMessage :: String -> T.Text -> (DAP.ExceptionDetails -> IO ()) -> TestDAP ()
    assertExcMessage descr expected k = do
      -- Read next "stopped" event and its thread id
      Event{eventBody = Just DAP.StoppedEvent{DAP.stoppedEventReason, DAP.stoppedEventThreadId}}
        <- waitFiltering EventTy "stopped"
      liftIO $ assertEqual (descr ++ ": should be an exception") DAP.StoppedEventReasonException stoppedEventReason
      let tid = maybe (error "no tid?") id stoppedEventThreadId
      info <- exceptionInfo tid
      let details = DAP.exceptionInfoResponseDetails info
      liftIO $ case details of
        Nothing -> assertFailure $ descr ++ ": exception details should be present"
        Just d  -> do
          assertEqual (descr ++ ": message")
            (Just (T.unpack expected)) (DAP.exceptionDetailsMessage d)
          k d
      continueThread tid

    checkNested :: TestDAP ()
    checkNested = do
      assertExcMessage "first exception" "outer boom" $ \details -> do
        case DAP.exceptionDetailsInnerException details of
          Nothing -> pure ()
          Just [] -> pure ()
          Just _  -> assertFailure "First exception should not have inner exceptions"
      assertExcMessage "second exception" "inner boom" $ const $ pure ()
      assertExcMessage "third exception" "inner boom"  $ const $ pure ()
      assertExcMessage "fourth exception" "inner boom" $ const $ pure ()
      assertExcMessage "fifth exception" "inner boom"  $ const $ pure ()
      waitFiltering_ EventTy "terminated"