packages feed

errors-ext-0.4.2: test/Control/Error/Extensions/Spec.hs

--
-- Copyright 2017, 2018 Warlock <internalmike@gmail.com>
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

module Control.Error.Extensions.Spec
  ( tests
  ) where

import Test.HUnit.Base hiding (Label)
import Control.Error.Extensions
import Control.Exception
import Control.Monad.Error.Class
import Control.Monad.Loops
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import qualified Data.ByteString as S (ByteString)
import Data.Conduit hiding (ConduitM)
import qualified Data.Conduit.Combinators as N
import Data.Conduit.Lift
import Data.Conduit.Parsers.Binary.Get
import Data.Conduit.Parsers.Binary.Put
import Data.IORef
import System.IO.Error

tests :: Test
tests = TestList
  $  ((TestCase . runBracketTest) <$> bracketTests)
  ++ [TestCase testPrevNaturalsFile]

data Resource = Resource { acquired :: !Bool, released :: !Bool, processed :: !Bool } deriving (Eq, Show)

resource :: IO (IORef Resource)
resource = newIORef $ Resource False False False

type Acquire = IORef Resource -> ExceptT String IO (IORef Resource)

acquireOk :: Acquire
acquireOk r = do
  lift $ modifyIORef' r $ \x -> x { acquired = True }
  return r

acquireEx :: Acquire
acquireEx _ = lift $ fail "acquireEx"

acquireEr :: Acquire
acquireEr _ = throwE "acquireEr"

type Release = IORef Resource -> ExceptT String IO ()

releaseOk :: Release
releaseOk r = lift $ modifyIORef' r $ \x -> x { released = True }

releaseEx :: Release
releaseEx _ = lift $ fail "releaseEx"

releaseEr :: Release
releaseEr _ = throwE "releaseEr"

type Process = IORef Resource -> ExceptT String IO Resource

processOk :: Process
processOk r = lift $ do
  v <- readIORef r
  modifyIORef' r $ \x -> x { processed = True }
  return v

processEx :: Process
processEx _ = lift $ fail "processEx"

processEr :: Process
processEr _ = throwE "processEr"

data Err = Err { isException :: !Bool, message :: !String } deriving (Eq, Show)

bracketTest :: Acquire -> Release -> Process -> IO (Resource, Either Err Resource)
bracketTest acquire release process = do
  r <- resource
  p <- catch (either (\x -> Left $ Err False x) Right <$> (runExceptT $ bracketE (acquire r) release process)) $ \e ->
    let _ = e :: IOError in return $ Left $ Err True $ ioeGetErrorString e
  f <- readIORef r
  return (f, p)

data TestData = TestData String !Acquire !Release !Process !(Resource, Either Err Resource)

bracketTests :: [TestData]
bracketTests =
  [ TestData "1" acquireOk releaseOk processOk (Resource True True True, Right $ Resource True False False)
  , TestData "2" acquireEx releaseOk processOk (Resource False False False, Left $ Err { isException = True, message = "acquireEx" })
  , TestData "3" acquireEr releaseOk processOk (Resource False False False, Left $ Err { isException = False, message = "acquireEr" })
  , TestData "4" acquireOk releaseEx processOk (Resource True False True, Left $ Err { isException = True, message = "releaseEx" })
  , TestData "5" acquireOk releaseEr processOk (Resource True False True, Left $ Err { isException = False, message = "releaseEr" })
  , TestData "6" acquireOk releaseOk processEx (Resource True True False, Left $ Err { isException = True, message = "processEx" })
  , TestData "7" acquireOk releaseOk processEr (Resource True True False, Left $ Err { isException = False, message = "processEr" })
  , TestData "8" acquireOk releaseEx processEx (Resource True False False, Left $ Err { isException = True, message = "processEx" })
  , TestData "9" acquireOk releaseEr processEx (Resource True False False, Left $ Err { isException = True, message = "processEx" })
  , TestData "10" acquireOk releaseEx processEr (Resource True False False, Left $ Err { isException = False, message = "processEr" })
  , TestData "11" acquireOk releaseEr processEr (Resource True False False, Left $ Err { isException = False, message = "processEr" })
  ]

runBracketTest :: TestData -> Assertion
runBracketTest (TestData m a r p e) = do
  t <- bracketTest a r p
  assertEqual m e t

data PrevNaturalError = NotNatural Integer | Minimal deriving (Eq, Show)

prevNatural :: (Monad m, Integral i) => ConduitT i i (ExceptT PrevNaturalError m) ()
prevNatural = awaitForever $ \ !n -> do
  if n <= 0 then throwError (NotNatural $ fromIntegral n) else return ()
  if n == 1 then throwError Minimal else return ()
  yield $ n - 1

prevNaturalsFile :: Monad m => ConduitT S.ByteString S.ByteString (ExceptT PrevNaturalError m) ()
prevNaturalsFile = (eitherVoidR <$> runGet (iterateM_ (const $ yield =<< getInt8) (error ""))) .| prevNatural .| awaitForever (runPut . putInt8)

testPrevNaturalsFile :: Assertion
testPrevNaturalsFile = do
  assertEqual "" (Left Minimal, "") $ runConduitPure $ yield "\x01\x02\x03" .| runExceptC prevNaturalsFile `fuseBoth` N.sinkLazy
  assertEqual "" (Right (), "\x02\x07\x08") $ runConduitPure $ yield "\x03\x08\x09" .| runExceptC prevNaturalsFile `fuseBoth` N.sinkLazy