{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.Conduit.ProcessSpec (spec, main) where

import Test.Hspec
import Test.Hspec.QuickCheck (prop)
import Data.Conduit
import qualified Data.Conduit.Combinators as CC
import qualified Data.Conduit.List as CL
import Data.Conduit.Process
import Control.Concurrent.Async (concurrently)
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8
import System.Exit
import Control.Concurrent (threadDelay)

main :: IO ()
main = hspec spec

spec :: Spec
spec = describe "Data.Conduit.Process" $ do
#ifndef WINDOWS
    prop "cat" $ \wss -> do
        let lbs = L.fromChunks $ map S.pack wss
        ((sink, closeStdin), source, Inherited, cph) <- streamingProcess (shell "cat")
        ((), bss) <- concurrently
            (do
                runConduit $ mapM_ yield (L.toChunks lbs) .| sink
                closeStdin)
            (runConduit $ source .| CL.consume)
        L.fromChunks bss `shouldBe` lbs
        ec <- waitForStreamingProcess cph
        ec `shouldBe` ExitSuccess

    it "closed stream" $ do
        (ClosedStream, source, Inherited, cph) <- streamingProcess (shell "cat")
        bss <- runConduit $ source .| CL.consume
        bss `shouldBe` []

        ec <- waitForStreamingProcess cph
        ec `shouldBe` ExitSuccess

    it "handles sub-process exit code" $ do
        (sourceCmdWithConsumer "exit 0" CL.sinkNull)
                `shouldReturn` (ExitSuccess, ())
        (sourceCmdWithConsumer "exit 11" CL.sinkNull)
                `shouldReturn` (ExitFailure 11, ())
        (sourceCmdWithConsumer "exit 12" CL.sinkNull)
                `shouldReturn` (ExitFailure 12, ())
        (sourceCmdWithStreams "exit 0" CL.sourceNull CL.sinkNull CL.sinkNull)
                `shouldReturn` (ExitSuccess, (), ())
        (sourceCmdWithStreams "exit 11" CL.sourceNull CL.sinkNull CL.sinkNull)
                `shouldReturn` (ExitFailure 11, (), ())
        (sourceCmdWithStreams "exit 12" CL.sourceNull CL.sinkNull CL.sinkNull)
                `shouldReturn` (ExitFailure 12, (), ())

    it "consumes stdout" $ do
        let mystr = "this is a test string" :: String
        sourceCmdWithStreams ("bash -c \"echo -n " ++ mystr ++ "\"")
                             CL.sourceNull
                             CL.consume -- stdout
                             CL.consume -- stderr
                `shouldReturn` (ExitSuccess, [S8.pack mystr], [])

    it "consumes stderr" $ do
        let mystr = "this is a test string" :: String
        sourceCmdWithStreams ("bash -c \">&2 echo -n " ++ mystr ++ "\"")
                             CL.sourceNull
                             CL.consume -- stdout
                             CL.consume -- stderr
                `shouldReturn` (ExitSuccess, [], [S8.pack mystr])

    it "feeds stdin" $ do
        let mystr = "this is a test string" :: S.ByteString
        sourceCmdWithStreams "cat"
                             (yield mystr)
                             CL.consume -- stdout
                             CL.consume -- stderr
                `shouldReturn` (ExitSuccess, [mystr], [])
    it "gracefully handles closed stdin" $ do
        let blob = L.iterate (+1) 0
            blobHead = L.toStrict $ L.take 10000 blob
        sourceCmdWithStreams "head -c 10000"
                             (CC.sourceLazy blob)
                             (L.toStrict <$> CC.sinkLazy) -- stdout
                             CL.consume -- stderr
                `shouldReturn` (ExitSuccess, blobHead, [])
#endif
    it "blocking vs non-blocking" $ do
        (ClosedStream, ClosedStream, ClosedStream, cph) <- streamingProcess (shell "sleep 1")

        mec1 <- getStreamingProcessExitCode cph
        mec1 `shouldBe` Nothing

        threadDelay 1500000

        -- For slow systems where sleep may take longer than 1.5 seconds, do
        -- this in a loop.
        let loop 0 = error "Took too long for sleep to exit, your system is acting funny"
            loop i = do
                mec2 <- getStreamingProcessExitCode cph
                case mec2 of
                    Nothing -> do
                        threadDelay 500000
                        loop (pred i)
                    Just _ -> mec2 `shouldBe` Just ExitSuccess
        loop (5 :: Int)

        ec <- waitForStreamingProcess cph
        ec `shouldBe` ExitSuccess
