Skip to content

Commit 201cf1e

Browse files
committed
Add test for terminating "cabal run" on unix
1 parent e3790c7 commit 201cf1e

File tree

5 files changed

+99
-2
lines changed

5 files changed

+99
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Control.Concurrent (killThread, threadDelay, myThreadId)
2+
import Control.Exception (finally)
3+
import qualified System.Posix.Signals as Signal
4+
import System.Exit (exitFailure)
5+
6+
main = do
7+
writeFile "exe.run" "up and running"
8+
mainThreadId <- myThreadId
9+
Signal.installHandler Signal.sigTERM (Signal.Catch $ killThread mainThreadId) Nothing
10+
sleep
11+
`finally` putStrLn "exiting"
12+
where
13+
sleep = do
14+
putStrLn "about to sleep"
15+
threadDelay 10000000 -- 10s
16+
putStrLn "done sleeping"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: RunKill
2+
version: 1.0
3+
build-type: Simple
4+
cabal-version: >= 1.10
5+
6+
executable exe
7+
default-language: Haskell2010
8+
build-depends: base, process, unix
9+
main-is: Main.hs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
packages: .
2+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import Test.Cabal.Prelude
2+
import qualified System.Process as Process
3+
import Control.Concurrent (threadDelay)
4+
import System.Directory (removeFile)
5+
import Control.Exception (catch, throwIO)
6+
import System.IO.Error (isDoesNotExistError)
7+
8+
{-
9+
This test verifies that 'cabal run' terminates its
10+
child when it is killed. More generally, while we
11+
use the same code path for all child processes, this
12+
ensure that cabal-install cleans up after all children.
13+
(That might change if 'cabal run' is changed to exec(3)
14+
without forking in the future.)
15+
-}
16+
17+
main :: IO ()
18+
main = cabalTest $ do
19+
skipIfWindows -- test project relies on Posix
20+
21+
dir <- fmap testCurrentDir getTestEnv
22+
let runFile = dir </> "exe.run"
23+
liftIO $ removeFile runFile `catchNoExist` return ()
24+
25+
cabal_raw_action ["v2-build", "exe"] (\_ -> return ())
26+
r <- fails $ cabal_raw_action ["v2-run", "exe"] $ \cabalHandle -> do
27+
-- wait for "cabal run" to have started "exe"
28+
waitFile total runFile
29+
-- then kill "cabal run"
30+
Process.terminateProcess cabalHandle
31+
32+
-- "exe" should exit, and should have been interrupted before
33+
-- finishing its sleep
34+
assertOutputContains "exiting" r
35+
assertOutputDoesNotContain "done sleeping" r
36+
37+
where
38+
catchNoExist action handle =
39+
action `catch`
40+
(\e -> if isDoesNotExistError e then handle else throwIO e)
41+
waitFile totalWait f
42+
| totalWait <= 0 = error "waitFile timed out"
43+
| otherwise = readFile f `catchNoExist` do
44+
threadDelay delta
45+
waitFile (totalWait - delta) f
46+
delta = 50000 -- 0.05s
47+
total = 10000000 -- 10s
48+
49+
cabal_raw_action :: [String] -> (Process.ProcessHandle -> IO ()) -> TestM Result
50+
cabal_raw_action args action = do
51+
configured_prog <- requireProgramM cabalProgram
52+
env <- getTestEnv
53+
r <- liftIO $ runAction (testVerbosity env)
54+
(Just (testCurrentDir env))
55+
(testEnvironment env)
56+
(programPath configured_prog)
57+
args
58+
Nothing
59+
action
60+
recordLog r
61+
requireSuccess r

cabal-testsuite/src/Test/Cabal/Run.hs

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
-- | A module for running commands in a chatty way.
33
module Test.Cabal.Run (
44
run,
5+
runAction,
56
Result(..)
67
) where
78

@@ -24,8 +25,14 @@ data Result = Result
2425

2526
-- | Run a command, streaming its output to stdout, and return a 'Result'
2627
-- with this information.
27-
run :: Verbosity -> Maybe FilePath -> [(String, Maybe String)] -> FilePath -> [String] -> Maybe String -> IO Result
28-
run _verbosity mb_cwd env_overrides path0 args input = do
28+
run :: Verbosity -> Maybe FilePath -> [(String, Maybe String)] -> FilePath -> [String]
29+
-> Maybe String -> IO Result
30+
run verbosity mb_cwd env_overrides path0 args input =
31+
runAction verbosity mb_cwd env_overrides path0 args input (\_ -> return ())
32+
33+
runAction :: Verbosity -> Maybe FilePath -> [(String, Maybe String)] -> FilePath -> [String]
34+
-> Maybe String -> (ProcessHandle -> IO ()) -> IO Result
35+
runAction _verbosity mb_cwd env_overrides path0 args input action = do
2936
-- In our test runner, we allow a path to be relative to the
3037
-- current directory using the same heuristic as shells:
3138
-- 'foo' refers to an executable in the PATH, but './foo'
@@ -71,6 +78,8 @@ run _verbosity mb_cwd env_overrides path0 args input = do
7178
Nothing -> error "No stdin handle when input was specified!"
7279
Nothing -> return ()
7380

81+
action procHandle
82+
7483
-- wait for the program to terminate
7584
exitcode <- waitForProcess procHandle
7685
out <- wait sync

0 commit comments

Comments
 (0)