kazura-queue-0.1.0.3: test/Test/Expectations.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Test.Expectations
( module Test.Expectations
, module EX
) where
import Test.Hspec.Expectations as EX hiding (shouldBe, shouldContain,
shouldNotBe, shouldReturn, shouldSatisfy,
shouldThrow)
import qualified Test.Concurrent as T
import qualified Test.Util as T
import qualified Test.HUnit as HU
import qualified Control.Concurrent.Async as AS
import qualified Control.Exception as E
import qualified Control.Monad as M
import qualified GHC.Conc as CC
import qualified Data.List as L
import qualified Data.Maybe as MB
import Data.Monoid ((<>))
import qualified Data.Typeable as TP
import qualified System.Mem as Mem
import qualified System.Mem.Weak as Weak
import qualified System.Timeout as ST
import Prelude hiding (and, fail, or)
assertFailure :: String -> IO x
assertFailure desc = do
M.void $ HU.assertFailure desc
error "dummy"
failWithException :: (String -> String) -> IO x -> IO y
failWithException descf wait = proc `E.catch` returnError
where
returnError (E.SomeException err) = do
assertFailure . descf $ "but aborted with: " <> show err
proc = do
M.void wait
assertFailure $ descf "died with no exception (test maybe wrong)"
--------------------
-- assertions
assertTrue :: Bool -> String -> IO ()
assertTrue True _ = return ()
assertTrue False desc = HU.assertFailure desc
assertEqual :: (Show x, Eq x) => x -> x -> IO ()
assertEqual expected actual = assertTrue (expected == actual) desc
where
desc = mkDesc 80 "expected" expectedStr <> "\n" <>
mkDesc 80 "actual " actualStr
expectedStr = show expected
actualStr = show actual
assertNotEqual :: (Show x, Eq x) => x -> x -> IO ()
assertNotEqual expected actual = assertTrue (expected /= actual) desc
where
desc = mkDesc 80 "expected not to be" expectedStr <> "\n" <>
mkDesc 80 "actual" actualStr
expectedStr = show expected
actualStr = show actual
shouldBe :: (Show x, Eq x) => x -> x -> IO ()
shouldBe = flip assertEqual
shouldNotBe :: (Show x, Eq x) => x -> x -> IO ()
shouldNotBe = flip assertNotEqual
shouldSatisfy :: Show x => x -> (x -> Bool) -> IO ()
shouldSatisfy x f = assertTrue (f x) desc
where
desc = "assertion failed on: " ++ show x
shouldContain :: (Show x, Eq x) => [x] -> [x] -> IO ()
shouldContain actual expected = assertTrue result $ desc
where
result = expected `L.isInfixOf` actual
desc = mkDesc 80 "expected to contain" expectedStr <> "\n" <>
mkDesc 80 "but actual" actualStr
expectedStr = show expected
actualStr = show actual
shouldReturn :: (Show x, Eq x) => IO x -> x -> IO ()
shouldReturn act expected = do
ex <- E.try act
case ex of
Right x -> x `shouldBe` expected
Left (E.SomeException err) -> HU.assertFailure $ desc err
where
desc err = mkDesc 80 "expected" expectedStr <> "\n" <>
mkDesc 80 "but exception throwed" (show err)
expectedStr = show expected
shouldThrow :: (Show x, E.Exception e) => IO x -> Selector e -> IO ()
shouldThrow act selector = do
ex <- E.try act
case ex of
Right x -> HU.assertFailure $ descX x
Left (err :: E.SomeException) -> case E.fromException err of
Just e | selector e -> return ()
| otherwise -> HU.assertFailure $ descF err
Nothing -> HU.assertFailure $ descE err
where
descX x = expectedStr <> mkDesc 80 "but returned" (show x)
descF e = expectedStr <> mkDesc 80 "but selector failed for" (show e)
descE e = expectedStr <> mkDesc 80 "but exception throwed" (show e)
expectedStr = mkDesc 80 "expected to throw" exceptedType <> "\n"
exceptedType = (show . TP.typeOf . instanceOf) selector
instanceOf :: Selector a -> a
instanceOf _ = error "dummy data of shouldThrow"
-- gc expectations
shouldBeGarbageCollected :: Weak.Weak x -> IO ()
shouldBeGarbageCollected weak = do
Mem.performGC
mref <- Weak.deRefWeak weak
case mref of
Just _ -> assertFailure "expected to be garbage collected but does not"
Nothing -> return ()
shouldNotBeGarbageCollected :: Weak.Weak x -> IO ()
shouldNotBeGarbageCollected weak = do
Mem.performGC
mv <- Weak.deRefWeak weak
case mv of
Just _ -> return ()
Nothing -> assertFailure "expected not to be garbage collected but garbage collected"
-- concurrent expectations
shouldBlock :: IO x -> Int -> IO (AS.Async x)
shouldBlock act time = do
async <- AS.async act
mstatus <- ST.timeout time $ T.waitStop async
case mstatus of
Just CC.ThreadFinished -> HU.assertFailure $ desc "but finished"
Just CC.ThreadDied -> failWithException desc $ AS.wait async
Just (CC.ThreadBlocked _) -> return ()
_ -> HU.assertFailure $ desc "but still running"
return async
where
desc str = "expected to block in " <> show time <> " nanosec\n" <> str
shouldStillBlock :: AS.Async x -> Int -> IO ()
shouldStillBlock async time = M.void $ AS.wait async `shouldBlock` time
shouldAwakeFinish :: AS.Async x -> Int -> IO x
shouldAwakeFinish async time = do
mstatus <- ST.timeout time $ T.waitFinish async
status <- MB.fromMaybe (T.threadStatus async) $ return <$> mstatus
let wait = AS.wait async
case status of
CC.ThreadFinished -> return ()
CC.ThreadDied -> failWithException desc $ AS.wait async
CC.ThreadBlocked reason ->
HU.assertFailure . desc $ "but still blocked with " <> show reason
CC.ThreadRunning ->
HU.assertFailure . desc $ "but still running"
wait
where
desc str = "expected to awake and finish in " <>
show time <> " nanosec\n" <> str
onlyOneShouldAwakeFinish :: [AS.Async x] -> Int -> IO (x, [AS.Async x])
onlyOneShouldAwakeFinish asyncs0 time = do
mret <- ST.timeout time $ T.waitFinishAny asyncs0
(idx, status) <- case mret of
Just [ret] -> return ret
Just _ -> assertFailure . desc $ "but not only one finished"
Nothing -> assertFailure . desc $ "but still running"
let (async, asyncs1) = T.pickUp idx asyncs0
wait = AS.wait async
x <- case status of
CC.ThreadFinished -> wait
CC.ThreadDied -> failWithException desc wait
_ -> assertFailure . desc $ "unknown case"
return (x, asyncs1)
where
desc str = "expected to awake only one and finish in " <>
show time <> " nanosec\n" <> str
shouldNotBlock :: IO x -> Int -> IO x
shouldNotBlock act time = do
async <- AS.async act
mstatus <- ST.timeout time $ T.waitStop async
let wait = AS.wait async
case mstatus of
Just CC.ThreadFinished -> return ()
Just CC.ThreadDied -> failWithException desc wait
Just (CC.ThreadBlocked reason) -> do
HU.assertFailure . desc $ "but blocked with " <> show reason
_ -> HU.assertFailure $ desc "but still running"
wait
where
desc str = "expected not to block and finish in " <>
show time <> " nanosec\n" <> str
shouldFinish :: IO x -> Int -> IO x
shouldFinish act time = do
async <- AS.async act
mstatus <- ST.timeout time $ T.waitFinish async
let wait = AS.wait async
case mstatus of
Just CC.ThreadFinished -> return ()
Just CC.ThreadDied -> failWithException desc wait
Just (CC.ThreadBlocked reason) -> do
HU.assertFailure . desc $ "but blocked with " <> show reason
_ -> HU.assertFailure $ desc "but still running"
wait
where
desc str = "expected to finish in " <> show time <>
" nanosec\n" <> str
---------------------------
--- util
mkDesc :: Int -> String -> String -> String
mkDesc len s1 s2
| length s1 + length str > len = s1 <> ":\n\t" <> str
| otherwise = s1 <> ": " <> str
where
str = truncateString 512 s2
truncateString :: Int -> String -> String
truncateString len str
| null rest = res
| otherwise = res <> "..."
where
(res, rest) = L.splitAt len str
and :: [a -> Bool] -> (a -> Bool)
and (c:cs) a | c a = and cs a
| otherwise = False
and [] _ = True