{-# LANGUAGE StandaloneDeriving, LambdaCase #-}
module Main where
import Control.Concurrent (killThread, forkIO)
import Control.Monad (Monad(..), mapM)
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as BS (unpack)
import qualified Data.List as List ((++), map)
import Data.Maybe (Maybe(..))
import Network.WebSockets
import System.IO (IO(..))
import Test.Tasty
import Test.Tasty.HUnit (assertEqual, assertBool, testCase)
import Language.Haskell.Tools.Demo (ResponseMsg(..), ClientMessage(..), runDemo)
main :: IO ()
main = do -- create one daemon process for the whole testing session
-- with separate processes it is not a problem
threadId <- forkIO $ runDemo []
defaultMain allTests
killThread threadId
allTests :: TestTree
allTests
= localOption (mkTimeout ({- 10s -} 1000 * 1000 * 10))
$ testGroup "daemon-tests"
[ testGroup "simple-tests" $ map makeDemoTest simpleTests
, testGroup "loading-tests" $ map makeDemoTest loadingTests
, testGroup "refactor-tests" $ map makeDemoTest refactorTests
, astViewTest
]
simpleTests :: [(String, [ClientMessage], [ResponseMsg])]
simpleTests =
[ ( "empty-test", [], [] )
, ( "keep-alive", [KeepAlive], [] )
]
loadingTests :: [(String, [ClientMessage], [ResponseMsg])]
loadingTests =
[ ( "one-module"
, [InitialProject [("A", "module A where\n\na = ()")]]
, [] )
, ( "multi-modules"
, [InitialProject [("A", "module A where\n\na = ()"), ("B", "module B where\n\nimport A\n\nb = a")]]
, [] )
, ( "multi-modules-wrong-order"
, [InitialProject [("B", "module B where\n\nimport A\n\nb = a"), ("A", "module A where\n\na = ()")]]
, [] )
, ( "multi-modules-added-later"
, [ InitialProject [("A", "module A where\n\na = ()")]
, ModuleChanged "B" "module B where\n\nimport A\n\nb = a" ]
, [] )
, ( "comp-problem"
, [InitialProject [("A", "module A where\n\na = noSuchVar")]]
, [CompilationProblem "A.hs 3:5\nVariable not in scope: noSuchVar\n\n"] )
]
refactorTests :: [(String, [ClientMessage], [ResponseMsg])]
refactorTests =
[ ( "simple-refactor"
, [ InitialProject [("A", "module A where\n\na = ()")]
, PerformRefactoring "RenameDefinition" "A" "3:1-3:2" ["x"] ]
, [ RefactorChanges [("A", Just "module A where\n\nx = ()")] ] )
, ( "multi-module-refactor"
, [ InitialProject [("A", "module A where\n\na = ()"), ("B", "module B where\n\nimport A\n\nb = a")]
, PerformRefactoring "RenameDefinition" "A" "3:1-3:2" ["x"] ]
, [ RefactorChanges [("A", Just "module A where\n\nx = ()"), ("B", Just "module B where\n\nimport A\n\nb = x")] ] )
, ( "multi-module-refactor-user-added-later"
, [ InitialProject [("A", "module A where\n\na = ()")]
, ModuleChanged "B" "module B where\n\nimport A\n\nb = a"
, PerformRefactoring "RenameDefinition" "A" "3:1-3:2" ["x"] ]
, [ RefactorChanges [("A", Just "module A where\n\nx = ()"), ("B", Just "module B where\n\nimport A\n\nb = x")] ] )
, ( "multi-module-refactor-both-added-later"
, [ InitialProject [("B", "module B where")]
, ModuleChanged "A" "module A where\n\na = ()"
, ModuleChanged "B" "module B where\n\nimport A\n\nb = a"
, PerformRefactoring "RenameDefinition" "A" "3:1-3:2" ["x"] ]
, [ RefactorChanges [("A", Just "module A where\n\nx = ()"), ("B", Just "module B where\n\nimport A\n\nb = x")] ] )
, ( "rename-module"
, [ InitialProject [("A", "module A where\n\na = ()")]
, PerformRefactoring "RenameDefinition" "A" "1:8-1:9" ["AA"] ]
, [ RefactorChanges [("AA", Just "module AA where\n\na = ()"), ("A", Nothing)] ] )
, ( "change-module"
, [ InitialProject [("A", "module A where\n\na = ()")]
, ModuleChanged "A" "module A where\n\n\na = ()"
, PerformRefactoring "RenameDefinition" "A" "4:1-4:2" ["x"] ]
, [ RefactorChanges [("A", Just "module A where\n\n\nx = ()")] ] )
, ( "removed-module"
, [ InitialProject [("A", "module A where\n\na = ()"), ("B", "module B where\n\nimport A\n\nb = a")]
, ModuleDeleted "B"
, PerformRefactoring "RenameDefinition" "A" "3:1-3:2" ["x"] ]
, [ RefactorChanges [("A", Just "module A where\n\nx = ()")] ] )
]
makeDemoTest :: (String, [ClientMessage], [ResponseMsg]) -> TestTree
makeDemoTest (label, input, expected) = testCase label $ do
actual <- communicateWithDemo input
assertEqual "" expected actual
astViewTest :: TestTree
astViewTest = testCase "ast-view-test" $ do
actual <- communicateWithDemo [ InitialProject [("A", "module A where\n\na = ()")]
, PerformRefactoring "UpdateAST" "A" "1:1" [] ]
assertBool "The response must be a simple ASTViewContent message"
$ case actual of [ ASTViewContent _ ] -> True
_ -> False
communicateWithDemo :: [ClientMessage] -> IO [ResponseMsg]
communicateWithDemo msgs = runClient "127.0.0.1" 8206 "/" $ \conn -> do
mapM (sendTextData conn . encode) (msgs ++ [Disconnect])
receiveAllResponses conn
receiveAllResponses :: Connection -> IO [ResponseMsg]
receiveAllResponses conn = do
Text mess <- receiveDataMessage conn
let decoded = decode mess
case decoded of Just Disconnected -> return []
Just other -> (other :) <$> receiveAllResponses conn
_ -> error ("Unrecognized response: " ++ BS.unpack mess)
deriving instance Eq ResponseMsg
instance FromJSON ResponseMsg
instance ToJSON ClientMessage