{-# LANGUAGE TemplateHaskell #-} module Main where import Control.Exception (SomeException) import qualified Control.Exception as E import Control.Monad.Reader hiding (reader) import Control.Pipe import Control.Pipe.Combinators import Control.Pipe.Exception import qualified Control.Pipe.Binary as PB import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as BC import Data.IORef import Data.List import Prelude hiding (catch) import Test.HUnit import Test.Framework.Providers.HUnit import Test.Framework.TH.Prime import System.IO data Action = OpenFile FilePath IOMode | CloseFile FilePath | CaughtException E.IOException deriving (Eq, Show) type Report = IORef [Action] type M = ReaderT Report IO runPipeM :: Pipeline M r -> IO (Either SomeException r, [Action]) runPipeM p = do r <- newIORef [] result <- E.try $ runReaderT (runPipe p) r acts <- readIORef r return (result, reverse acts) saveAction :: Action -> M () saveAction act = do r <- ask liftIO . modifyIORef r $ (act:) open :: FilePath -> IOMode -> M Handle open fp mode = do saveAction (OpenFile fp mode) liftIO $ openFile fp mode close :: FilePath -> Handle -> M () close fp h = do liftIO $ hClose h saveAction (CloseFile fp) reader :: FilePath -> Producer ByteString M () reader fp = fReader >+> PB.lines where fReader = bracket (open fp ReadMode) (close fp) PB.handleReader -- line-by-line writer with verbose initializer and finalizer writer :: FilePath -> Consumer ByteString M () writer fp = pipe (`BC.snoc` '\n') >+> fWriter where fWriter = do x <- await feed x $ bracket (open fp WriteMode) (close fp) PB.handleWriter equalFiles :: FilePath -> FilePath -> Assertion equalFiles fp1 fp2 = do content1 <- readFile fp1 content2 <- readFile fp2 content1 @=? content2 assertLeft :: Show b => Either a b -> (a -> Assertion) -> Assertion assertLeft x f = either f err x where err b = assertFailure $ "expected Left, got " ++ show b assertRight :: Show a => Either a b -> (b -> Assertion) -> Assertion assertRight x f = either err f x where err a = assertFailure $ "expected Right, got " ++ show a tmpOutput :: FilePath tmpOutput = "dist/build/testtmp" case_cp :: Assertion case_cp = do let input = "README.md" (r, acts) <- runPipeM $ reader input >+> writer tmpOutput assertRight r $ \_ -> return () acts @=? [ OpenFile input ReadMode , OpenFile tmpOutput WriteMode , CloseFile input , CloseFile tmpOutput ] equalFiles input tmpOutput isNonexistingException :: SomeException -> Assertion isNonexistingException e = "does not exist" `isInfixOf` show e @? "expected 'no such file' exception, " ++ "got " ++ show e case_unopenable :: Assertion case_unopenable = do let input = "README.md" output = "/unopenable/file" (result, acts) <- runPipeM $ reader input >+> writer output assertLeft result isNonexistingException acts @=? [ OpenFile input ReadMode , OpenFile output WriteMode , CloseFile input ] case_join :: Assertion case_join = do let input1 = "README.md" input2 = "LICENSE" (r, acts) <- runPipeM $ (reader input1 >> reader input2) >+> writer tmpOutput assertRight r $ \_ -> return () acts @=? [ OpenFile input1 ReadMode , OpenFile tmpOutput WriteMode , CloseFile input1 , OpenFile input2 ReadMode , CloseFile input2 , CloseFile tmpOutput ] content1 <- readFile input1 content2 <- readFile input2 content3 <- readFile tmpOutput content3 @=? content1 ++ content2 case_recover :: Assertion case_recover = do let input1 = "README.md" input2 = "/nonexistent/file" safeReader fp = catch (reader fp) $ \e -> lift $ saveAction (CaughtException e) isException (CaughtException e) = isNonexistingException (E.toException e) isException x = assertFailure $ "expected exception, got " ++ show x (r, acts) <- runPipeM $ (safeReader input1 >> safeReader input2) >+> writer tmpOutput assertRight r $ \_ -> return () zipWithM_ (flip ($)) acts [ (@=? OpenFile input1 ReadMode) , (@=? OpenFile tmpOutput WriteMode) , (@=? CloseFile input1) , (@=? OpenFile input2 ReadMode) , isException , (@=? CloseFile tmpOutput) ] equalFiles input1 tmpOutput main :: IO () main = $(defaultMainGenerator)