packages feed

ide-backend-0.9.0: TestSuite/TestSuite/Tests/Debugger.hs

module TestSuite.Tests.Debugger (testGroupDebugger) where

import Prelude hiding (span, mod)
import Control.Monad
import Data.List (sort)
import Data.Monoid
import Test.HUnit
import Test.Tasty
import qualified Data.ByteString.Lazy.Char8 as L (unlines)
import qualified Data.ByteString.Lazy.UTF8  as L
import qualified Data.ByteString.Lazy       as L
import qualified Data.Text                  as T

import IdeSession
import TestSuite.State
import TestSuite.Session
import TestSuite.Assertions

testGroupDebugger :: TestSuiteEnv -> TestTree
testGroupDebugger env = testGroup "Debugger" [
    stdTest env "Setting and clearing breakpoints"        testSettingAndClearing
  , stdTest env "Running until breakpoint, then resuming" testResuming
  , stdTest env "Printing and forcing"                    testPrintingAndForcing
  ]

testSettingAndClearing :: TestSuiteEnv -> Assertion
testSettingAndClearing env = withAvailableSession env $ \session -> do
    skipTest "Debugger API known broken"

    updateSessionD session qsort 1
    assertNoErrors session

    expTypes <- getExpTypes session
    let (modName, mouseSpan) = mkSpan "Main" (2, 16, 2, 16)
        fullSpan = fst . last $ expTypes modName mouseSpan
        (_, wrongSpan) = mkSpan "Main" (0, 0, 0, 0)

    r1 <- setBreakpoint session modName fullSpan True
    r2 <- setBreakpoint session modName fullSpan False
    r3 <- setBreakpoint session modName fullSpan False
    r4 <- setBreakpoint session modName wrongSpan True
    assertEqual "original value of breakpoint"  (Just False) r1
    assertEqual "breakpoint successfully set"   (Just True)  r2
    assertEqual "breakpoint successfully unset" (Just False) r3
    assertEqual "invalid breakpoint"            Nothing      r4

testResuming :: TestSuiteEnv -> Assertion
testResuming env = withAvailableSession env $ \session -> do
    skipTest "Debugger API known broken"

    updateSessionD session qsort 1
    assertNoErrors session

    expTypes <- getExpTypes session
    let (modName, mouseSpan) = mkSpan "Main" (2, 16, 2, 16)
        fullSpan = fst . last $ expTypes modName mouseSpan

    Just False <- setBreakpoint session modName fullSpan True

    let inputList :: [Integer]
        inputList = [8, 4, 0, 3, 1, 23, 11, 18]

    outputs <- forM (mark inputList) $ \(i, isFirst, _isLast) -> do
       runActions <- if isFirst then runStmt session "Main" "main"
                                else resume session
       (output, RunBreak) <- runWaitAll runActions
       assertBreak session "Main"
                           "Main.hs@2:16-2:48"
                           "[a]"
                           [ ("_result" , "[Integer]" , "_")
                           , ("a"       , "Integer"   , show i)
                           , ("left"    , "[Integer]" , "_")
                           , ("right"   , "[Integer]" , "_")
                           ]
       return output

    do runActions <- resume session
       (finalOutput, finalResult) <- runWaitAll runActions
       let output = L.concat $ outputs ++ [finalOutput]
       assertEqual "" RunOk finalResult
       assertEqual "" (show (sort inputList) ++ "\n") (L.toString output)
       mBreakInfo <- getBreakInfo session
       assertEqual "" Nothing mBreakInfo

testPrintingAndForcing :: TestSuiteEnv -> Assertion
testPrintingAndForcing env = withAvailableSession env $ \session -> do
    skipTest "Debugger API known broken"

    updateSessionD session qsort 1
    assertNoErrors session

    expTypes <- getExpTypes session
    let (modName, mouseSpan) = mkSpan "Main" (2, 16, 2, 16)
        fullSpan = fst . last $ expTypes modName mouseSpan

    Just False <- setBreakpoint session modName fullSpan True
    runActions <- runStmt session "Main" "main"
    (_output, RunBreak) <- runWaitAll runActions

    printed <- printVar session "left" True False
    forced  <- printVar session "left" True True
    assertEqual "" [("left", "[Integer]", "(_t1::[Integer])")] printed
    assertEqual "" [("left", "[Integer]", "[4, 0, 3, 1]")] forced

{-------------------------------------------------------------------------------
  Auxiliary
-------------------------------------------------------------------------------}

qsort :: IdeSessionUpdate
qsort = (updateSourceFile "Main.hs" . L.unlines $ [
          --          1         2         3         4         5
          -- 12345678901234567890123456789012345678901234567890123456
            "qsort [] = [] "
          , "qsort (a:as) = qsort left ++ [a] ++ qsort right"
          , "  where (left,right) = (filter (<=a) as, filter (>a) as)"
          , ""
          , "main = print (qsort [8, 4, 0, 3, 1, 23, 11, 18])"
          ])
     <> (updateCodeGeneration True)

-- | Mark each element of the list with booleans indicating whether it's the
-- first and/or the last element in the list
mark :: [a] -> [(a, Bool, Bool)]
mark []     = []
mark [x]    = [(x, True, True)]
mark (x:xs) = (x, True, False) : aux xs
  where
    aux []     = error "impossible"
    aux [y]    = [(y, False, True)]
    aux (y:ys) = (y, False, False) : aux ys

assertBreak :: IdeSession
            -> String                     -- ^ Module
            -> String                     -- ^ Location
            -> String                     -- ^ Result type
            -> [(String, String, String)] -- ^ Var, type, value
            -> Assertion
assertBreak session mod loc resTy vars = do
  Just BreakInfo{..} <- getBreakInfo session
  assertEqual      "module name" mod   (T.unpack breakInfoModule)
  assertEqual      "location"    loc   (show breakInfoSpan)
  assertAlphaEquiv "result type" resTy (T.unpack breakInfoResultType)
  assertEqual      "number of local vars" (length vars) (length breakInfoVariableEnv)
  forM_ (zip vars breakInfoVariableEnv) $ \((var, typ, val), (var', typ', val')) -> do
    assertEqual      "var name" var (T.unpack var')
    assertAlphaEquiv "var type" typ (T.unpack typ')
    assertEqual      "var val"  val (T.unpack val')