{-# LANGUAGE StandaloneDeriving, LambdaCase #-}
module Main where
import Test.Tasty
import Test.Tasty.HUnit
import System.Exit
import System.Directory
import System.FilePath
import Control.Monad
import Control.Exception
import Control.Concurrent
import Control.Concurrent.MVar
import Network.WebSockets
import qualified Data.ByteString.Lazy.Char8 as BS
import qualified Data.List as List
import Data.Aeson
import Data.Maybe
import System.IO
import System.Directory
import Debug.Trace
import Language.Haskell.Tools.Demo
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