packages feed

shh-0.5.0.0: test/Test.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ExtendedDefaultRules #-}
module Main where

import System.Directory
import Shh
import Test.DocTest
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Control.Exception
import Control.Monad
import Data.Char
import Data.Word
import Control.Concurrent.Async
import System.IO

load SearchPath
    ["wc", "head", "tr", "echo", "cat", "true", "false", "mktemp", "sleep"
    , "rm", "printf", "xargs", "find"
    ]

main = do
    putStrLn "################################################"
    putStrLn " These tests require that certain binaries"
    putStrLn " exist on your $PATH. If you are getting"
    putStrLn " failures, please check that it's not because"
    putStrLn " they are missing."
    putStrLn "################################################"
    doctest ["--fast", "-isrc", "src/Shh/Internal.hs"]
    defaultMain tests

tests :: TestTree
tests = localOption (Timeout 4000000 "4s") $ testGroup "Tests" [unitTests, properties]

bytesToString :: [Word8] -> String
bytesToString = map (chr . fromIntegral)

properties :: TestTree
properties = testGroup "Properties"
    [ testProperty "trim = trim . trim" $ \l -> trim l == trim (trim l)
    , testProperty "encodeIdentifier creates a unique encoding"
        $ \(l1,l2) -> (encodeIdentifier l1 == encodeIdentifier l2) == (l1 == l2)
    , testProperty "writeOutput" $ \s -> ioProperty $ do
        let
            s' = bytesToString s
        k <- writeOutput s' |> capture
        pure $ s' === k
    , testProperty "pureProc id" $ \s -> ioProperty $ do
        let
            s' = bytesToString s
        k <- readProc $ s' >>> pureProc id
        pure $ s' === k
    , testProperty "pureProc (map toUpper)" $ \(ASCIIString s) -> ioProperty $ do
        k <- readProc $ s >>> pureProc (map toUpper)
        pure $ map toUpper s === k
    , testProperty "pureProc . const === writeOutput" $ \s -> ioProperty $ do
        let
            s' = bytesToString s
        a <- writeOutput s' |> capture
        b <- pureProc (const s') |> capture
        pure $ a === b
    , testProperty "writeOutput s |> capture >>= writeOutput |> capture === s"
        $ \(ASCIIString s) -> ioProperty $ do
            r <- writeOutput s |> capture >>= writeOutput |> capture
            pure $ r === s
    , testProperty "pureProc id === readInputP (\\s -> writeOutput s)"
        $ \(ASCIIString s) -> ioProperty $ do
            a <- writeOutput s |> pureProc id |> capture
            b <- writeOutput s |> readInputP (\s -> writeOutput s) |> capture
            pure $ a === b
    ]

withTmp :: (FilePath -> IO a) -> IO a
withTmp = bracket (readTrim mktemp) rm

unitTests :: TestTree
unitTests = testGroup "Unit tests"
    [ testCase "Read stdout" $ do
        l <- readProc $ echo "test"
        l @?= "test\n"
    , testCase "Redirect to /dev/null" $ do
        l <- readProc $ echo "test" &> devNull
        l @?= ""
    , testCase "Redirct stderr" $ do
        l <- echo "test" &> StdErr |!> capture
        l @?= "test\n"
    , testCase "Redirect to file (Truncate)" $ withTmp $ \t -> do
        echo "test" &> Truncate t
        r <- readProc $ cat t
        "test\n" @?= r
    , testCase "Redirect to file (Append)" $ withTmp $ \t -> do
        echo "test" &> Truncate t
        echo "test" &> Append t
        r <- readProc $ cat t
        "test\ntest\n" @?= r
    , testCase "Long pipe" $ do
        r <- readProc $ echo "test" |> tr "-d" "e" |> tr "-d" "s"
        r @?= "tt\n"
    , testCase "Pipe stderr" $ replicateM_ 100 $ do
        r <- readProc $ echo "test" &> StdErr |!> cat
        r @?= "test\n"
    , testCase "Lazy read" $ replicateM_ 100 $ do
        withRead (cat "/dev/zero") $ \s -> do
            take 6 s @?= "\0\0\0\0\0\0"
    , testCase "Multiple outputs" $ do
        l <- readProc $ (echo (1 :: Int) >> echo (2 :: Int)) |> cat
        l @?= "1\n2\n"
    , testCase "Terminate upstream processes" $ do
        Left x <- catchFailure (mkProc "false" ["dummy"] |> (sleep 1 >> false "Didn't kill"))
        x @?= Shh.Failure "false" ["dummy"] 1
    , testCase "Write to process" $ withTmp $ \t -> do
        writeProc (cat &> Truncate t) "Hello"
        r <- readProc (cat t)
        r @?= "Hello"
        writeProc (cat &> Truncate t) "Goodbye"
        r <- readProc (cat t)
        r @?= "Goodbye"
    , testCase "apply" $ do
        r <- apply (tr "-d" "es") "test"
        r @?= "tt"
    , testCase "ignoreFailure" $ replicateM_ 30 $ do
        r <- readProc $ ignoreFailure false |> echo "Hello"
        r @?= "Hello\n"
    , testCase "Read failure" $ replicateM_ 30 $ do
        Left r <- catchFailure $ readProc $ false "dummy"
        r @?= Shh.Failure "false" ["dummy"] 1
    , testCase "Read failure chain start" $ replicateM_ 30 $ do
        Left r <- catchFailure $ readProc $ false "dummy" |> echo "test" |> true
        r @?= Shh.Failure "false" ["dummy"] 1
    , testCase "Read failure chain middle" $ replicateM_ 30 $ do
        Left r <- catchFailure $ readProc $ echo "test" |> false "dummy" |> true
        r @?= Shh.Failure "false" ["dummy"] 1
    , testCase "Read failure chain end" $ replicateM_ 30 $ do
        Left r <- catchFailure $ readProc $ echo "test" |> true |> false "dummy"
        r @?= Shh.Failure "false" ["dummy"] 1
    , testCase "Lazy read checks code" $ replicateM_ 30 $ do
        Left r <- catchFailure $ withRead (cat "/dev/urandom" |> false "dummy") $ pure . take 3
        r @?= Shh.Failure "false" ["dummy"] 1
    , testCase "Identifier odd chars" $ encodeIdentifier "1@3.-" @?= "_1'40'3''_"
    , testCase "Identifier make lower" $ encodeIdentifier "T.est" @?= "_T''est"
    , testCase "pureProc closes input" $ do
        r <- readProc $ cat "/dev/urandom" |> pureProc (const "test")
        r @?= "test"
    , testCase "pureProc closes output" $ do
        r <- readProc $ pureProc (const "test") |> cat
        r @?= "test"
    , testCase "pureProc doesn't close std handles" $ do
        runProc $ pureProc (const "")
        b <- hIsOpen stdin
        b @?= True
        b <- hIsOpen stdout
        b @?= True
        runProc $ pureProc (const "") &> StdErr
        b <- hIsOpen stderr
        b @?= True
    , testCase "pureProc sanity check" $ do
        r <- readProc $ printf "Hello" |> pureProc id |> cat
        r @?= "Hello"
    , testCase "bind nativeProc" $ do
        r <- writeOutput "te" >> writeOutput "st" |> capture
        r @?= "test"
    , testCase "stdin interleave capture" $ do
        r <- writeOutput "te" >> writeError "--" &!> devNull >> writeOutput "st" >> writeError "--" &!> devNull |> capture
        r @?= "test"
    , testCase "stderr interleave capture" $ do
        r <- writeOutput "--" &> devNull >> writeError "te" >> writeOutput "--" &> devNull >> writeError "st" |!> capture
        r @?= "test"
    , testCase "prefixLines" $ do
        r <- printf "Hello\\nWorld" |> prefixLines ":" |> capture
        r @?= ":Hello\n:World\n"
    , testCase "Bind in the middle" $ do
        l <- echo "a" |> prefixLines ":" >> echo "c" |> prefixLines ":" |> capture
        l @?= "::a\n:c\n"
    , testCase "writeOutput mimics printf" $ do
        l <- writeOutput "a\0b\0c" |> capture
        r <- printf "a\\0b\\0c" |> capture
        l @?= r
    , testCase "xargs1 printf" $ do
        a <- printf "a\\0b\\0c" |> xargs "--null" "-L1" "echo" |> capture
        b <- printf "a\\0b\\0c" |> xargs1 "\0" echo |> capture
        a @?= b
    , testCase "xargs1 writeOutput" $ do
        a <- writeOutput "a\0b\0c" |> xargs "--null" "-L1" "echo" |> capture
        b <- writeOutput "a\0b\0c" |> xargs1 "\0" echo |> capture
        a @?= b
    , testCase "fixity |>" $ do
        a <- echo "a" >> echo "b" >> echo "c" |> Main.head "-n" 2 |> capture
        b <- (echo "a" >> echo "b" >> echo "c") |> Main.head "-n" 2 |> capture
        a @?= b
    , testCase "pureProc . const === writeOutput (spam test)" $ replicateM_ 3000 $ do
        let
            s = ""
        a <- writeOutput s |> capture
        b <- pureProc (const s) |> capture
        a @?= b
    , testCase "complex example with intermediate handles (>BUFSIZ)" $ do
        let c = 20000000
        s <- readTrim $ cat "/dev/urandom" |> readInputP (\s -> writeOutput (map toUpper s) |> cat) |> Main.head "-c" c |> wc "-c"
        show c @?= s
    ]