module TestSuite.Tests.UpdateTargets (testGroupUpdateTargets) where import Data.Monoid import System.Exit import System.FilePath import System.Process import Test.Tasty import Test.HUnit import qualified Data.ByteString.Lazy.UTF8 as L import qualified Data.Text as T import IdeSession import TestSuite.State import TestSuite.Session import TestSuite.Assertions testGroupUpdateTargets :: TestSuiteEnv -> TestTree testGroupUpdateTargets env = testGroup "Update targets" [ stdTest env "[{} < A, {A} < B, {A} < C], require [A]" test_1 , stdTest env "[{} < A, {A} < B, {A} < C], require [A], error in B" test_2 , stdTest env "[{} < A, {A} < B, {A} < C], require [B]" test_3 , stdTest env "[{} < A, {A} < B, {A} < C], require [B], error in C" test_4 , stdTest env "[{} < A, {A} < B, {A} < C], require [A], error in A" test_5 , stdTest env "[{} < A, {A} < B, {A} < C], require [B], error in A" test_6 , stdTest env "[{} < A, {A} < B, {A} < C], require [B], error in B" test_7 , stdTest env "[{} < A, {A} < B, {A} < C], require [B, C]" test_8 , stdTest env "[{} < A, {A} < B, {A} < C], require [B, C], then [B]" test_9 , stdTest env "[{} < A, {A} < B, {A} < C], require [B, C], then [B] with modified B" test_10 , stdTest env "[{} < A, {A} < B, {A} < C], require [B, C], then [B] with modified B and error in C" test_11 , stdTest env "[{} < A, {A} < B, {A} < C], require [B, C], then [B] with error in C" test_12 , stdTest env "Switch from one to another relative include path for the same module name with TargetsInclude" test_Switch_TargetsInclude , stdTest env "Switch from one to another relative include path for the same module name with TargetsExclude" test_Switch_TargetsExclude , stdTest env "Switch from one to another relative include path with TargetsInclude and the main module not in path" test_Switch_TargetsInclude_MainNotInPath ] test_1 :: TestSuiteEnv -> Assertion test_1 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "0" , modCn "0" , updateTargets (TargetsInclude ["A.hs"]) ]) 1 assertNoErrors session assertLoadedModules session "" ["A"] buildExeTargetHsSucceeds env session "A" test_2 :: TestSuiteEnv -> Assertion test_2 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "invalid" , modCn "0" , updateTargets (TargetsInclude ["A.hs"]) ]) 1 assertNoErrors session assertLoadedModules session "" ["A"] buildExeTargetHsSucceeds env session "A" test_3 :: TestSuiteEnv -> Assertion test_3 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "0" , modCn "0" , updateTargets (TargetsInclude ["B.hs"]) ]) 2 assertNoErrors session assertLoadedModules session "" ["A", "B"] buildExeTargetHsSucceeds env session "B" test_4 :: TestSuiteEnv -> Assertion test_4 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "0" , modCn "invalid" , updateTargets (TargetsInclude ["B.hs"]) ]) 2 assertNoErrors session assertLoadedModules session "" ["A", "B"] buildExeTargetHsSucceeds env session "B" buildExeTargetHsFails env session "C" test_5 :: TestSuiteEnv -> Assertion test_5 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "invalid" , modBn "0" , modCn "0" , updateTargets (TargetsInclude ["A.hs"]) ]) 1 assertOneError session assertLoadedModules session "" [] buildExeTargetHsFails env session "A" test_6 :: TestSuiteEnv -> Assertion test_6 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "invalid" , modBn "0" , modCn "0" , updateTargets (TargetsInclude ["B.hs"]) ]) 2 assertOneError session assertLoadedModules session "" [] buildExeTargetHsFails env session "B" test_7 :: TestSuiteEnv -> Assertion test_7 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "invalid" , modCn "0" , updateTargets (TargetsInclude ["B.hs"]) ]) 2 assertOneError session assertLoadedModules session "" ["A"] buildExeTargetHsFails env session "B" -- Only fails due to -- "Source errors encountered. Not attempting to build executables." -- buildExeTargetHsSucceeds session "A" test_8 :: TestSuiteEnv -> Assertion test_8 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "0" , modCn "0" , updateTargets (TargetsInclude ["B.hs", "C.hs"]) ]) 3 assertNoErrors session assertLoadedModules session "" ["A", "B", "C"] autocomplete <- getAutocompletion session assertEqual "we have autocompletion info for C" 2 $ length (autocomplete "C" "sp") -- span, split buildExeTargetHsSucceeds env session "B" buildExeTargetHsSucceeds env session "C" test_9 :: TestSuiteEnv -> Assertion test_9 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "0" , modCn "0" , updateTargets (TargetsInclude ["B.hs", "C.hs"]) ]) 3 assertNoErrors session assertLoadedModules session "" ["A", "B", "C"] do autocomplete <- getAutocompletion session assertEqual "we have autocompletion info for C" 2 $ length (autocomplete "C" "sp") -- span, split buildExeTargetHsSucceeds env session "C" updateSessionD session (mconcat [ updateTargets (TargetsInclude ["B.hs"]) ]) 2 assertLoadedModules session "" ["A", "B"] do autocomplete <- getAutocompletion session assertEqual "we no longer have autocompletion info for C" 0 $ length (autocomplete "C" "sp") -- span, split buildExeTargetHsSucceeds env session "B" test_10 :: TestSuiteEnv -> Assertion test_10 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "0" , modCn "0" , updateTargets (TargetsInclude ["B.hs", "C.hs"]) ]) 3 assertNoErrors session assertLoadedModules session "" ["A", "B", "C"] do autocomplete <- getAutocompletion session assertEqual "we have autocompletion info for C" 2 $ length (autocomplete "C" "sp") -- span, split buildExeTargetHsSucceeds env session "C" updateSessionD session (mconcat [ modBn "1" , updateTargets (TargetsInclude ["B.hs"]) ]) 2 assertLoadedModules session "" ["A", "B"] do autocomplete <- getAutocompletion session assertEqual "autocompletion info for C cleared" 0 $ length (autocomplete "C" "sp") buildExeTargetHsSucceeds env session "B" test_11 :: TestSuiteEnv -> Assertion test_11 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "0" , modCn "0" , updateTargets (TargetsInclude ["B.hs", "C.hs"]) ]) 3 assertNoErrors session assertLoadedModules session "" ["A", "B", "C"] do autocomplete <- getAutocompletion session assertEqual "we have autocompletion info for C" 2 $ length (autocomplete "C" "sp") -- span, split updateSessionD session (mconcat [ modBn "1" , modCn "invalid" , updateTargets (TargetsInclude ["B.hs"]) ]) 2 assertLoadedModules session "" ["A", "B"] do autocomplete <- getAutocompletion session assertEqual "autocompletion info for C cleared" 0 $ length (autocomplete "C" "sp") buildExeTargetHsFails env session "C" buildExeTargetHsSucceeds env session "B" test_12 :: TestSuiteEnv -> Assertion test_12 env = withAvailableSession env $ \session -> do updateSessionD session (mconcat [ modAn "0" , modBn "0" , modCn "0" , updateTargets (TargetsInclude ["B.hs", "C.hs"]) ]) 3 assertNoErrors session assertLoadedModules session "" ["A", "B", "C"] do autocomplete <- getAutocompletion session assertEqual "we have autocompletion info for C" 2 $ length (autocomplete "C" "sp") -- span, split updateSessionD session (mconcat [ modCn "invalid" , updateTargets (TargetsInclude ["B.hs"]) ]) 2 assertLoadedModules session "" ["A", "B"] do autocomplete <- getAutocompletion session assertEqual "autocompletion info for C cleared" 0 $ length (autocomplete "C" "sp") buildExeTargetHsFails env session "C" buildExeTargetHsSucceeds env session "B" test_Switch_TargetsInclude :: TestSuiteEnv -> Assertion test_Switch_TargetsInclude env = withAvailableSession env $ \session -> do loadModulesFrom' session "TestSuite/inputs/AnotherA" (TargetsInclude ["TestSuite/inputs/AnotherA/A.hs"]) assertOneError session updateSessionD session (updateCodeGeneration True) 0 updateSessionD session (updateSourceFileFromFile "TestSuite/inputs/ABnoError/B.hs") 0 assertOneError session updateSessionD session (updateSourceFileFromFile "TestSuite/inputs/AnotherB/B.hs") 0 assertOneError session updateSessionD session (updateRelativeIncludes ["", "TestSuite/inputs/AnotherA", "TestSuite/inputs/ABnoError"]) 2 -- note the recompilation assertNoErrors session do runActions <- runStmt session "Main" "main" (output, _) <- runWaitAll runActions assertEqual "output" "\"running 'A depends on B, no errors' from ABnoError\"\n" output let m = "Main" distDir <- getDistDir session ifTestingExe env $ do let updE = buildExe [] [(T.pack m, "TestSuite/inputs/AnotherA/A.hs")] updateSessionD session updE 4 -- TODO: Some modules may be compiled more than once (#189) status <- getBuildExeStatus session assertEqual "after exe build" (Just ExitSuccess) status (stExc, out, _) <- readProcessWithExitCode (distDir "build" m m) [] [] assertEqual "A throws exception" (ExitFailure 1) stExc assertEqual "exe output with old include path" "\"running 'A depends on B, no errors' from ABnoError\"\n" out runActionsExe <- runExe session m (outExe, statusExe) <- runWaitAll runActionsExe assertEqual "Output from runExe" "\"running 'A depends on B, no errors' from ABnoError\"\nMain: A.hs throws exception\n" outExe assertEqual "after runExe" (ExitFailure 1) statusExe let updE2 = buildExe [] [(T.pack m, "A.hs")] updateSessionD session updE2 0 status2 <- getBuildExeStatus session assertEqual "after exe build2" (Just ExitSuccess) status2 (stExc2, out2, _) <- readProcessWithExitCode (distDir "build" m m) [] [] assertEqual "A throws exception" (ExitFailure 1) stExc2 assertEqual "exe output with old include path" "\"running 'A depends on B, no errors' from ABnoError\"\n" out2 runActionsExe2 <- runExe session m (outExe2, statusExe2) <- runWaitAll runActionsExe2 assertEqual "Output from runExe 2" "\"running 'A depends on B, no errors' from ABnoError\"\nMain: A.hs throws exception\n" outExe2 assertEqual "after runExe" (ExitFailure 1) statusExe2 updateSessionD session (updateRelativeIncludes ["TestSuite/inputs/AnotherA", "TestSuite/inputs/AnotherB"]) 2 assertNoErrors session do runActions3 <- runStmt session "Main" "main" (output3, _) <- runWaitAll runActions3 assertEqual "output3" "\"running A with another B\"\n" output3 updateSessionD session (updateSourceFileDelete "TestSuite/inputs/ABnoError/B.hs") 0 -- already recompiled above assertNoErrors session do runActions35 <- runStmt session "Main" "main" (output35, _) <- runWaitAll runActions35 assertEqual "output35" "\"running A with another B\"\n" output35 -- And this one works OK even without updateSourceFileDelete -- and even without session restart. ifTestingExe env $ do let updE3 = buildExe [] [(T.pack m, "TestSuite/inputs/AnotherA/A.hs")] updateSessionD session updE3 1 status3 <- getBuildExeStatus session -- Path "" no longer in include paths here! assertEqual "after exe build3" (Just $ ExitFailure 1) status3 let updE4 = buildExe [] [(T.pack m, "A.hs")] updateSessionD session updE4 2 status4 <- getBuildExeStatus session assertEqual "after exe build4" (Just ExitSuccess) status4 (stExc4, out4, _) <- readProcessWithExitCode (distDir "build" m m) [] [] assertEqual "A throws exception" (ExitFailure 1) stExc4 assertEqual "exe output with new include path" "\"running A with another B\"\n" out4 runActionsExe4 <- runExe session m (outExe4, statusExe4) <- runWaitAll runActionsExe4 assertEqual "Output from runExe 4" "\"running A with another B\"\nMain: A.hs throws exception\n" outExe4 assertEqual "after runExe" (ExitFailure 1) statusExe4 test_Switch_TargetsExclude :: TestSuiteEnv -> Assertion test_Switch_TargetsExclude env = withAvailableSession env $ \session -> do loadModulesFrom' session "TestSuite/inputs/AnotherA" (TargetsExclude []) assertOneError session updateSessionD session (updateCodeGeneration True) 0 updateSessionD session (updateSourceFileFromFile "TestSuite/inputs/ABnoError/B.hs") 2 assertNoErrors session updateSessionD session (updateRelativeIncludes ["", "TestSuite/inputs/AnotherA", "TestSuite/inputs/ABnoError"]) 2 -- with TargetsExclude [], this is superfluous assertNoErrors session do runActions <- runStmt session "Main" "main" (output, _) <- runWaitAll runActions assertEqual "output" "\"running 'A depends on B, no errors' from ABnoError\"\n" output distDir <- getDistDir session let m = "Main" ifTestingExe env $ do let updE = buildExe [] [(T.pack m, "TestSuite/inputs/AnotherA/A.hs")] updateSessionD session updE 4 -- TODO: Some modules may be compiled more than once (#189) status <- getBuildExeStatus session assertEqual "after exe build" (Just ExitSuccess) status (stExc, out, _) <- readProcessWithExitCode (distDir "build" m m) [] [] assertEqual "A throws exception" (ExitFailure 1) stExc assertEqual "exe output with old include path" "\"running 'A depends on B, no errors' from ABnoError\"\n" out runActionsExe <- runExe session m (outExe, statusExe) <- runWaitAll runActionsExe assertEqual "Output from runExe" "\"running 'A depends on B, no errors' from ABnoError\"\nMain: A.hs throws exception\n" outExe assertEqual "after runExe" (ExitFailure 1) statusExe let updE2 = buildExe [] [(T.pack m, "A.hs")] updateSessionD session updE2 0 status2 <- getBuildExeStatus session assertEqual "after exe build2" (Just ExitSuccess) status2 (stExc2, out2, _) <- readProcessWithExitCode (distDir "build" m m) [] [] assertEqual "A throws exception" (ExitFailure 1) stExc2 assertEqual "exe output with old include path" "\"running 'A depends on B, no errors' from ABnoError\"\n" out2 runActionsExe2 <- runExe session m (outExe2, statusExe2) <- runWaitAll runActionsExe2 assertEqual "Output from runExe 2" "\"running 'A depends on B, no errors' from ABnoError\"\nMain: A.hs throws exception\n" outExe2 assertEqual "after runExe" (ExitFailure 1) statusExe2 updateSessionD session (updateSourceFileDelete "TestSuite/inputs/ABnoError/B.hs") 0 assertOneError session updateSessionD session (updateSourceFileFromFile "TestSuite/inputs/AnotherB/B.hs") 2 assertNoErrors session updateSessionD session (updateTargets (TargetsInclude ["TestSuite/inputs/AnotherA/A.hs"])) 0 assertOneError session updateSessionD session (updateRelativeIncludes ["TestSuite/inputs/AnotherA", "TestSuite/inputs/AnotherB"]) 2 -- with TargetsExclude, this would be superfluous assertNoErrors session -- fixed the error from above updateSessionD session (updateTargets (TargetsExclude [])) 2 -- recompilation due to session restart only assertNoErrors session do runActions3 <- runStmt session "Main" "main" (output3, _) <- runWaitAll runActions3 assertEqual "output3" "\"running A with another B\"\n" output3 ifTestingExe env $ do let updE3 = buildExe [] [(T.pack m, "TestSuite/inputs/AnotherA/A.hs")] updateSessionD session updE3 1 status3 <- getBuildExeStatus session -- Path "" no longer in include paths here! assertEqual "after exe build3" (Just $ ExitFailure 1) status3 let updE4 = buildExe [] [(T.pack m, "A.hs")] updateSessionD session updE4 2 status4 <- getBuildExeStatus session assertEqual "after exe build4" (Just ExitSuccess) status4 (stExc4, out4, _) <- readProcessWithExitCode (distDir "build" m m) [] [] assertEqual "A throws exception" (ExitFailure 1) stExc4 assertEqual "exe output with new include path" "\"running A with another B\"\n" out4 runActionsExe4 <- runExe session m (outExe4, statusExe4) <- runWaitAll runActionsExe4 assertEqual "Output from runExe 4" "\"running A with another B\"\nMain: A.hs throws exception\n" outExe4 assertEqual "after runExe" (ExitFailure 1) statusExe4 test_Switch_TargetsInclude_MainNotInPath :: TestSuiteEnv -> Assertion test_Switch_TargetsInclude_MainNotInPath env = withAvailableSession env $ \session -> do -- Since we set the target explicitly, ghc will need to be able to find -- the other module (B) on its own; that means it will need an include -- path to /TestSuite/inputs/ABnoError loadModulesFrom' session "TestSuite/inputs/ABnoError" (TargetsInclude ["TestSuite/inputs/ABnoError/A.hs"]) assertOneError session updateSessionD session (updateSourceFileFromFile "TestSuite/inputs/AnotherB/B.hs") 0 assertOneError session updateSessionD session (updateCodeGeneration True) 0 updateSessionD session (updateRelativeIncludes ["TestSuite/inputs/ABnoError"]) 2 -- note the recompilation assertNoErrors session runActions <- runStmt session "Main" "main" (output, _) <- runWaitAll runActions assertEqual "output" "\"running 'A depends on B, no errors' from ABnoError\"\n" output updateSessionD session (updateRelativeIncludes ["TestSuite/inputs/AnotherB"]) -- A not in path 2 assertNoErrors session runActions3 <- runStmt session "Main" "main" (output3, _) <- runWaitAll runActions3 assertEqual "output3" "\"running A with another B\"\n" output3 updateSessionD session (updateSourceFileDelete "TestSuite/inputs/ABnoError/B.hs") 0 -- already recompiled above assertNoErrors session do runActions35 <- runStmt session "Main" "main" (output35, _) <- runWaitAll runActions35 assertEqual "output35" "\"running A with another B\"\n" output35 distDir <- getDistDir session let m = "Main" let updE4 = buildExe [] [(T.pack m, "A.hs")] ifTestingExe env $ do updateSessionD session updE4 1 status4 <- getBuildExeStatus session assertEqual "after exe build4" (Just $ ExitFailure 1) status4 -- Failure due to no A in path. updateSessionD session (updateRelativeIncludes ["", "TestSuite/inputs/AnotherB"]) -- A still not in path 2 assertNoErrors session -- buildExe with full paths works though, if the includes have "" let updE41 = buildExe [] [(T.pack m, "TestSuite/inputs/ABnoError/A.hs")] ifTestingExe env $ do updateSessionD session updE41 3 -- TODO: Some modules may be compiled more than once (#189) status41 <- getBuildExeStatus session assertEqual "after exe build41" (Just ExitSuccess) status41 (stExc41, out41, _) <- readProcessWithExitCode (distDir "build" m m) [] [] assertEqual "A throws exception" (ExitFailure 1) stExc41 assertEqual "exe output with new include path" "\"running A with another B\"\n" out41 runActionsExe <- runExe session m (outExe, statusExe) <- runWaitAll runActionsExe assertEqual "Output from runExe 41" "\"running A with another B\"\nMain: A.hs throws exception\n" outExe assertEqual "after runExe" (ExitFailure 1) statusExe updateSessionD session (updateRelativeIncludes ["TestSuite/inputs/AnotherB", "TestSuite/inputs/ABnoError"]) -- A again in path 2 assertNoErrors session do runActions4 <- runStmt session "Main" "main" (output4, _) <- runWaitAll runActions4 assertEqual "output4" "\"running A with another B\"\n" output4 -- A again in path, so this time this works ifTestingExe env $ do updateSessionD session updE4 3 -- TODO: Some modules may be compiled more than once (#189) status45 <- getBuildExeStatus session assertEqual "after exe build45" (Just ExitSuccess) status45 (stExc45, out45, _) <- readProcessWithExitCode (distDir "build" m m) [] [] assertEqual "A throws exception" (ExitFailure 1) stExc45 assertEqual "exe output with new include path" "\"running A with another B\"\n" out45 runActionsExe4 <- runExe session m (outExe4, statusExe4) <- runWaitAll runActionsExe4 assertEqual "Output from runExe 45" "\"running A with another B\"\nMain: A.hs throws exception\n" outExe4 assertEqual "after runExe" (ExitFailure 1) statusExe4 updateSessionD session (updateRelativeIncludes ["TestSuite/inputs/ABnoError"]) 0 assertOneError session -- correct {------------------------------------------------------------------------------- Auxiliary -------------------------------------------------------------------------------} buildExeTargetHsSucceeds :: TestSuiteEnv -> IdeSession -> String -> IO () buildExeTargetHsSucceeds env session m = ifTestingExe env $ do let updE = buildExe [] [(T.pack m, m <.> "hs")] updateSessionD session updE 5 -- TODO: Main be compiled twice? (#189) distDir <- getDistDir session buildStderr <- readFile $ distDir "build/ide-backend-exe.stderr" assertEqual "buildStderr empty" "" buildStderr status <- getBuildExeStatus session assertEqual "after exe build" (Just ExitSuccess) status buildExeTargetHsFails :: TestSuiteEnv -> IdeSession -> String -> IO () buildExeTargetHsFails env session m = ifTestingExe env $ do let updE = buildExe [] [(T.pack m, m <.> "hs")] updateSessionD session updE 5 -- TODO: Main be compiled twice? (#189) status <- getBuildExeStatus session assertEqual "after exe build" (Just $ ExitFailure 1) status modAn, modBn, modCn :: String -> IdeSessionUpdate modAn n = updateSourceFile "A.hs" $ L.fromString $ unlines [ "module A (foo, main) where" , "foo :: Int" , "foo = " ++ n , "main :: IO ()" , "main = return ()" ] modBn n = updateSourceFile "B.hs" $ L.fromString $ unlines [ "module B (bar, main) where" , "import A (foo)" , "bar :: Int" , "bar = foo + " ++ n , "main :: IO ()" , "main = return ()" ] modCn n = updateSourceFile "C.hs" $ L.fromString $ unlines [ "module C (baz, main) where" , "import A (foo)" , "baz :: Int" , "baz = foo + " ++ n , "main :: IO ()" , "main = return ()" ]