packages feed

process-streaming-0.9.1.2: tests/test.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}

module Main where 

import Test.Tasty
import Test.Tasty.HUnit

import Data.Bifunctor
import Data.Monoid
import Data.Foldable
import Data.List.NonEmpty
import Data.ByteString
import Data.ByteString.Lazy as BL
import Data.Text.Lazy as TL
import Data.Text.Lazy.Encoding as TL
import Data.Typeable
import Data.Tree
import qualified Data.Attoparsec.Text as A
import Control.Applicative
import Control.Monad
import Control.Monad.Trans.Except
import Control.Exception
import Lens.Family (view)
import Pipes
import qualified Pipes.ByteString as B
import qualified Pipes.Prelude as P
import qualified Pipes.Parse as P
import qualified Pipes.Attoparsec as P
import qualified Pipes.Text as T
import qualified Pipes.Text.Encoding as T
import qualified Pipes.Text.IO as T
import qualified Pipes.Group as G
import qualified Pipes.Safe as S
import qualified Pipes.Safe.Prelude as S
import System.IO
import System.IO.Error
import System.Exit
import System.Directory
import System.Process.Streaming
import Pipes.Transduce 
import Pipes.Transduce.Text 
import qualified Pipes.Transduce.Text as PT
import Pipes.Transduce.ByteString 

main :: IO ()
main = defaultMain tests

tests :: TestTree
tests = testGroup "Tests" 
            [ testCollectStdoutStderrAsByteString
            , testFeedStdinCollectStdoutAsText  
            , testCombinedStdoutStderr
            , testInterruptExecution 
            , testFailIfAnythingShowsInStderr 
            , testTwoTextParsersInParallel  
            , testCountWords 
            , testDrainageDeadlock
            , testAlternatingWithCombined 
            , testDecodeFailure
            , testMultipleFeeds
            , testMultipleFeedsNoPiped
            ]

-------------------------------------------------------------------------------
testCollectStdoutStderrAsByteString :: TestTree
testCollectStdoutStderrAsByteString = testCase "collectStdoutStderrAsByteString" $ do
    r <- collectStdoutStderrAsByteString
    case r of
        ("ooo\nppp\n","eee\nffff\n") -> return ()
        _ -> assertFailure "oops"

collectStdoutStderrAsByteString :: IO (BL.ByteString,BL.ByteString)
collectStdoutStderrAsByteString = 
    execute
    (piped (shell "{ echo ooo ; echo eee 1>&2 ; echo ppp ;  echo ffff 1>&2 ; }"))
    (liftA2 (,) (foldOut intoLazyBytes) (foldErr intoLazyBytes))


-------------------------------------------------------------------------------
testFeedStdinCollectStdoutAsText  :: TestTree
testFeedStdinCollectStdoutAsText = testCase "feedStdinCollectStdoutAsText" $ do
    r <- feedStdinCollectStdoutAsText
    case r of
        "aaaaaa\naaaaa" -> return ()
        _ -> assertFailure "oops"

feedStdinCollectStdoutAsText :: IO Text
feedStdinCollectStdoutAsText = 
    execute
    (piped (shell "cat"))
    (feedUtf8 (Just "aaaaaa\naaaaa") *> foldOut (transduce1 utf8x intoLazyText))

-------------------------------------------------------------------------------

testCombinedStdoutStderr :: TestTree
testCombinedStdoutStderr = testCase "testCombinedStdoutStderr"  $ do
    r <- combinedStdoutStderr 
    case r of 
        (TL.lines -> ls) -> do
            assertEqual "line count" (Prelude.length ls) 4
            assertBool "expected lines" $ 
                getAll $ foldMap (All . flip Prelude.elem ls) $
                    [ "ooo"
                    , "ppp"
                    , "errprefix: eee"
                    , "errprefix: ffff"
                    ]

combinedStdoutStderr :: IO TL.Text
combinedStdoutStderr = execute
    (piped (shell "{ echo ooo ; echo eee 1>&2 ; echo ppp ;  echo ffff 1>&2 ; }"))
    (foldOutErr (combined (PT.lines utf8x) (groups annotate . PT.lines $ utf8x) intoLazyText))
  where
    annotate x = P.yield "errprefix: " *> x  

-------------------------------------------------------------------------------

testInterruptExecution :: TestTree
testInterruptExecution = localOption (mkTimeout $ 5*(10^6)) $
    testCase "interruptExecution" $ do
        r <- interruptExecution
        case r of
            Left "interrupted" -> return ()
            _ -> assertFailure "oops"

interruptExecution :: IO (Either String ())
interruptExecution = executeFallibly
    (piped (shell "sleep 100s"))
    (foldOut $ withFallibleCont $ \_ -> runExceptT . throwE $ "interrupted")

-------------------------------------------------------------------------------

testFailIfAnythingShowsInStderr :: TestTree
testFailIfAnythingShowsInStderr = localOption (mkTimeout $ 5*(10^6)) $
    testCase "failIfAnythingShowsInStderr" $ do
        r <- failIfAnythingShowsInStderr 
        case r of
            Left "morestuff\n" -> return ()
            _ -> assertFailure "oops"

failIfAnythingShowsInStderr :: IO (Either T.ByteString ())
failIfAnythingShowsInStderr = executeFallibly
    (piped (shell "{ echo morestuff 1>&2 ; sleep 100s ; }"))
    (foldErr trip)

-------------------------------------------------------------------------------

testTwoTextParsersInParallel  :: TestTree
testTwoTextParsersInParallel  = testCase "twoTextParsersInParallel" $ do
    r <- twoTextParsersInParallel
    case r of 
        Right ("ooooooo","aaaaaa") -> return ()
        _ -> assertFailure "oops"

parseChars :: Char -> A.Parser [Char] 
parseChars c = fmap mconcat $ 
    many (A.notChar c) *> A.many1 (some (A.char c) <* many (A.notChar c))
        
parser1 :: A.Parser [Char]
parser1 = parseChars 'o'

parser2 :: A.Parser [Char]
parser2 = parseChars 'a'

twoTextParsersInParallel :: IO (Either String ([Char], [Char]))
twoTextParsersInParallel = 
    executeFallibly
    (piped (shell "{ echo ooaaoo ; echo aaooaoa; }"))
    (foldOut (transduce1 utf8x $ 
                (,) <$> adapt parser1 <*> adapt parser2))
  where
    adapt p = withParser $ do
        r <- P.parse p
        return $ case r of
            Just (Right r') -> Right r'
            _ -> Left "parse error"
 
-------------------------------------------------------------------------------

testCountWords :: TestTree
testCountWords = testCase "testCountWords" $ do
    r <- countWords 
    case r of 
        3 -> return ()                   
        _ -> assertFailure "oops"

countWords :: IO Int
countWords = 
    execute
    (piped (shell "{ echo aaa ; echo bbb ; echo ccc ; }"))
    (foldOut (transduce1 PT.utf8x $
                withCont $ P.sum . G.folds const () (const 1) . view T.words))

-------------------------------------------------------------------------------

testDrainageDeadlock :: TestTree
testDrainageDeadlock = localOption (mkTimeout $ 20*(10^6)) $
    testCase "drainageDeadlock" $ do
        execute (piped (shell "chmod u+x tests/alternating.sh")) (pure ())
        r <- drainageDeadlock
        case r of
            ExitSuccess -> return ()
            _ -> assertFailure "oops"

-- A bug caused some streams not to be drained, and this caused problems
-- due to full output buffers.
drainageDeadlock :: IO ExitCode
drainageDeadlock = 
    execute
    (piped (proc "tests/alternating.sh" []))
    (foldErr (withCont $ \producer -> next producer >> pure ()) *> exitCode)

-------------------------------------------------------------------------------

testAlternatingWithCombined :: TestTree
testAlternatingWithCombined = localOption (mkTimeout $ 20*(10^6)) $
    testCase "testAlternatingWithCombined" $ do
        execute (piped (shell "chmod u+x tests/alternating.sh")) (pure ())
        r <- alternatingWithCombined  
        case r of 
            40000 -> return ()
            _ -> assertFailure $ "unexpected lines (1) " ++ show r
        r <- alternatingWithCombined2  
        case r of 
            (40000,40000) -> return ()
            _ -> assertFailure $ "unexpected lines (2) " ++ show r

alternatingWithCombined :: IO Integer
alternatingWithCombined = 
    execute
    (piped (proc "tests/alternating.sh" []))
    (foldOutErr (combined lp lp countLines))
  where
    lp = PT.lines PT.utf8x
    countLines = withCont $ P.sum . G.folds const () (const 1) . view T.lines


alternatingWithCombined2 :: IO (Integer,Integer)
alternatingWithCombined2 = 
    execute
    (piped (proc "tests/alternating.sh" []))
    (foldOutErr (combined lp lp (liftA2 (,) countLines countLines)))
  where
    lp = PT.lines PT.utf8x
    countLines = withCont $ P.sum . G.folds const () (const 1) . view T.lines

-------------------------------------------------------------------------------

testDecodeFailure :: TestTree
testDecodeFailure  = localOption (mkTimeout $ 20*(10^6)) $
    testCase "testDecodeFailure" $ do
        r <- decodeFailure
        case r of 
            Left _ -> return ()
            _ -> assertFailure "oops"

nonAscii :: BL.ByteString
nonAscii = TL.encodeUtf8 "\x4e2d"

decodeFailure :: IO (Either T.ByteString TL.Text)
decodeFailure = 
    executeFallibly 
    (piped (shell "cat"))
    (feedLazyBytes ("aaaaaaaa" <> nonAscii) *>
        foldOut (transduce1 (decoder T.decodeAscii) intoLazyText))

-------------------------------------------------------------------------------

testMultipleFeeds :: TestTree
testMultipleFeeds = testCase "testMultipleFeeds" $ do
    r <- multipleFeeds
    case r of
        "firstline1secondline2thirdline3" -> return ()
        _ -> assertFailure (show r)

multipleFeeds :: IO BL.ByteString
multipleFeeds = 
    execute
    (piped (shell "cat"))
    (feedBytes ["first","line1"] 
     *>
     feedBytes ["second","line2"] 
     *>
     feedBytes ["third","line3"] 
     *>
     foldOut intoLazyBytes)

testMultipleFeedsNoPiped :: TestTree
testMultipleFeedsNoPiped = localOption (mkTimeout $ 1*(10^6)) $ 
    testCase "testMultipleFeedsNoPiped" $ do
        r <- multipleFeedsNoPiped
        case r of
            "foo\n" -> return ()
            _ -> assertFailure (show r)

multipleFeedsNoPiped :: IO BL.ByteString
multipleFeedsNoPiped = 
    execute
    ((piped (shell "echo foo")) { std_in = Inherit })
    (feedBytes ["first","line1"] 
     *>
     feedBytes ["second","line2"] 
     *>
     feedBytes ["third","line3"] 
     *>
     foldOut intoLazyBytes)