packages feed

shake-0.16: src/Test/Errors.hs

module Test.Errors(main) where

import Development.Shake
import Development.Shake.FilePath
import Test.Type
import Data.List
import Control.Monad
import Control.Concurrent
import General.GetOpt
import Control.Exception.Extra hiding (assert)
import System.Directory as IO
import System.Time.Extra
import qualified System.IO.Extra as IO
import Data.Functor
import Prelude

data Args = Die deriving (Eq,Enum,Bounded,Show)

main = shakeTest test optionsEnum $ \args -> do
    "norule" %> \_ ->
        need ["norule_isavailable"]

    "failcreate" %> \_ ->
        return ()

    ["failcreates", "failcreates2"] &%> \_ ->
        writeFile' "failcreates" ""

    "recursive_" %> \out -> need ["intermediate_"]
    "intermediate_" %> \out -> need ["recursive_"]

    "rec1" %> \out -> need ["rec2"]
    "rec2" %> \out -> need ["rec1"]

    "systemcmd" %> \_ ->
        cmd "random_missing_command"

    "stack1" %> \_ -> need ["stack2"]
    "stack2" %> \_ -> need ["stack3"]
    "stack3" %> \_ -> error "crash"

    "staunch1" %> \out -> do
        liftIO $ sleep 0.1
        writeFile' out "test"
    "staunch2" %> \_ -> error "crash"

    let catcher out op = out %> \out -> do
            writeFile' out "0"
            op $ do src <- IO.readFile' out; writeFile out $ show (read src + 1 :: Int)
    catcher "finally1" $ actionFinally $ fail "die"
    catcher "finally2" $ actionFinally $ return ()
    catcher "finally3" $ actionFinally $ liftIO $ sleep 10
    catcher "finally4" $ actionFinally $ need ["wait"]
    "wait" ~> do liftIO $ sleep 10
    catcher "exception1" $ actionOnException $ fail "die"
    catcher "exception2" $ actionOnException $ return ()

    res <- newResource "resource_name" 1
    "resource" %> \out ->
        withResource res 1 $
            need ["resource-dep"]

    "overlap.txt" %> \out -> writeFile' out "overlap.txt"
    "overlap.t*" %> \out -> writeFile' out "overlap.t*"
    "overlap.*" %> \out -> writeFile' out "overlap.*"

    "chain.2" %> \out -> do
        src <- readFile' "chain.1"
        if src == "err" then error "err_chain" else writeFileChanged out src
    "chain.3" %> \out -> copyFile' "chain.2" out

    "tempfile" %> \out -> do
        file <- withTempFile $ \file -> do
            liftIO $ assertExists file
            return file
        liftIO $ assertMissing file
        withTempFile $ \file -> do
            liftIO $ assertExists file
            writeFile' out file
            fail "tempfile-died"

    "tempdir" %> \out -> do
        file <- withTempDir $ \dir -> do
            let file = dir </> "foo.txt"
            liftIO $ writeFile (dir </> "foo.txt") ""
                -- will throw if the directory does not exist
            writeFile' out ""
            return file
        liftIO $ assertMissing file

    phony "fail1" $ fail "die1"
    phony "fail2" $ fail "die2"

    when (Die `elem` args) $ action $ error "death error"

    "fresh_dir" %> \out -> liftIO $ createDirectoryIfMissing True out
    "need_dir" %> \out -> do
        liftIO $ createDirectoryIfMissing True "existing_dir"
        need ["existing_dir"]
        writeFile' out ""

    "persist_failure.1" %> \out -> do
        liftIO $ appendFile "persist_failure.log" "[pre]"
        need ["persist_failure.2"]
        liftIO $ appendFile "persist_failure.log" "[post]"
        writeFile' out ""
    "persist_failure.2" %> \out -> do
        src <- readFile' "persist_failure.3"
        liftIO $ print ("persist_failure.3", src)
        if src == "die" then do
            liftIO $ appendFile "persist_failure.log" "[err]"
            fail "die"
        else
            writeFileChanged out src

    "fast_failure" %> \out -> do
        liftIO $ sleep 0.1
        fail "die"
    "slow_success" %> \out -> do
        liftIO $ sleep 20
        writeFile' out ""

    -- not tested by default since only causes an error when idle GC is turned on
    phony "block" $
        liftIO $ putStrLn $ let x = x in x

test build = do
    let crash args parts = assertException parts (build $ "--quiet" : args)
    build ["clean"]
    build ["--sleep"]

    writeFile "chain.1" "x"
    build ["chain.3","--sleep"]
    writeFile "chain.1" "err"
    crash ["chain.3"] ["err_chain"]

    crash ["norule"] ["norule_isavailable"]
    crash ["failcreate"] ["failcreate"]
    crash ["failcreates"] ["failcreates"]
    crash ["recursive_"] ["recursive_","intermediate_","recursive"]
    crash ["rec1","rec2"] ["rec1","rec2","recursive"]
    crash ["systemcmd"] ["systemcmd","random_missing_command"]
    crash ["stack1"] ["stack1","stack2","stack3","crash"]

    b <- IO.doesFileExist "staunch1"
    when b $ removeFile "staunch1"
    crash ["staunch1","staunch2","-j2"] ["crash"]
    assertBoolIO (not <$> IO.doesFileExist "staunch1") "File should not exist, should have crashed first"
    crash ["staunch1","staunch2","-j2","--keep-going","--silent"] ["crash"]
    assertBoolIO (IO.doesFileExist "staunch1") "File should exist, staunch should have let it be created"

    crash ["finally1"] ["die"]
    assertContents "finally1" "1"
    build ["finally2"]
    assertContents "finally2" "1"
    crash ["exception1"] ["die"]
    assertContents "exception1" "1"
    build ["exception2"]
    assertContents "exception2" "0"

    forM_ ["finally3","finally4"] $ \name -> do
        t <- forkIO $ ignore $ build [name,"--exception"]
        retry 10 $ sleep 0.1 >> assertContents name "0"
        throwTo t (IndexOutOfBounds "test")
        retry 10 $ sleep 0.1 >> assertContents name "1"

    crash ["resource"] ["cannot currently call apply","withResource","resource_name"]

    build ["overlap.foo"]
    assertContents "overlap.foo" "overlap.*"
    build ["overlap.txt"]
    assertContents "overlap.txt" "overlap.txt"
    crash ["overlap.txx"] ["key matches multiple rules","2","overlap.txx"]

    crash ["tempfile"] ["tempfile-died"]
    src <- readFile "tempfile"
    assertMissing src
    build ["tempdir"]

    crash ["--die"] ["Shake","action","death error"]

    putStrLn "## BUILD errors"
    (out,_) <- IO.captureOutput $ build []
    assertBool ("nothing to do" `isInfixOf` out) $ "Expected 'nothing to do', but got: " ++ out

    putStrLn "## BUILD errors fail1 fail2 -k -j2"
    (out,_) <- IO.captureOutput $ try_ $ build ["fail1","fail2","-k","-j2",""]
    assertBool ("die1" `isInfixOf` out && "die2" `isInfixOf` out) $ "Expected 'die1' and 'die2', but got: " ++ out

    crash ["fresh_dir"] ["expected a file, got a directory","fresh_dir"]
    crash ["need_dir"] ["expected a file, got a directory","existing_dir"]

    -- check errors don't persist to the database, #428
    writeFile "persist_failure.log" ""
    writeFile "persist_failure.3" "test"
    build ["persist_failure.1","--sleep"]
    writeFile "persist_failure.3" "die"
    crash ["persist_failure.1","--sleep"] []
    assertContents "persist_failure.log" "[pre][post][err][pre]"
    writeFile "persist_failure.3" "test"
    build ["persist_failure.1","--sleep"]
    assertContents "persist_failure.log" "[pre][post][err][pre]"
    writeFile "persist_failure.3" "more"
    build ["persist_failure.1"]
    assertContents "persist_failure.log" "[pre][post][err][pre][pre][post]"

    -- check a fast failure aborts a slow success
    (t, _) <- duration $ crash ["fast_failure","slow_success","-j2"] ["die"]
    assertBool (t < 10) $ "Took too long, expected < 10, got " ++ show t