import Control.Monad (mapM_)
import Data.ByteString.Char8 (pack)
import qualified Control.Exception as E
import System.Environment (getArgs, setEnv, lookupEnv)
import System.Exit (ExitCode(ExitSuccess, ExitFailure) , exitWith)
import System.IO (stderr, hPutStr)
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
( Arbitrary
, arbitrary
, choose
, oneof
, listOf
)
import Test.Main
import Test.Main.Internal
newtype AExitCode = AExitCode ExitCode deriving Show
instance Arbitrary AExitCode where
arbitrary =
AExitCode <$> oneof [pure ExitSuccess, ExitFailure <$> choose (1, maxBound)]
newtype PrintableAsciiString =
PrintableAsciiString { getPrintableAsciiString :: String } deriving Show
-- https://en.wikipedia.org/wiki/ASCII#Character_set
instance Arbitrary PrintableAsciiString where
arbitrary = PrintableAsciiString <$> listOf (choose (' ', '~'))
main :: IO ()
main = hspec $
describe "Test.Main" $ do
prop "passes stdin and arguments to the program, and captures stdin data, stderr data, and exit code" $
\(argsA, inDataA, AExitCode eCode) -> do
let args = map getPrintableAsciiString argsA
inData = pack $ getPrintableAsciiString inDataA
testedMain = do
mapM_ putStrLn =<< getArgs
hPutStr stderr =<< getContents
exitWith eCode
actual <- withStdin inData $ withArgs args $ captureProcessResult testedMain
normalizeNewLines actual `shouldBe`
normalizeNewLines (ProcessResult (pack $ unlines args) inData eCode Nothing)
prop "captures stdin data, stderr data, exit code, and thrown exception when the program throws an exception" $
\(inDataA, errDataA, errorMesage) -> do
let inData = pack $ getPrintableAsciiString inDataA
errDataS = getPrintableAsciiString errDataA
errData = pack errDataS
testedMain = do
putStr =<< getContents
hPutStr stderr errDataS
_ <- fail errorMesage
putStrLn "*** THIS SHOULD NOT BE PRINTED! ***"
actual <- withStdin inData $ captureProcessResult testedMain
normalizeNewLines actual `shouldBe`
normalizeNewLines
( ProcessResult
inData
errData
(ExitFailure 1)
(Just $ E.SomeException $ userError errorMesage)
)
it "overwrites or deletes the specified environment variables temporarily" $ do
let vu = "value_to_unset"
vw = "value_to_overwrite"
nv = "new_value"
setEnv "ENV_VAR_TO_UNSET" vu
setEnv "ENV_VAR_TO_OVERWRITE" vw
let action =
(,) <$> lookupEnv "ENV_VAR_TO_UNSET" <*> lookupEnv "ENV_VAR_TO_OVERWRITE"
resultMutated <- withEnv
[("ENV_VAR_TO_UNSET", Nothing), ("ENV_VAR_TO_OVERWRITE", Just nv)]
action
resultMutated `shouldBe` (Nothing, Just nv)
action `shouldReturn` (Just vu, Just vw)