hspec-core-2.11.14: src/Test/Hspec/Core/QuickCheck/Util.hs
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE CPP #-}
module Test.Hspec.Core.QuickCheck.Util (
liftHook
, aroundProperty
, QuickCheckResult(..)
, Status(..)
, QuickCheckFailure(..)
, parseQuickCheckResult
, formatNumbers
, mkGen
, newSeed
#ifdef TEST
, stripSuffix
, splitBy
#endif
) where
import Prelude ()
import Test.Hspec.Core.Compat
import Data.Int
import System.Random
import Test.QuickCheck
import Test.QuickCheck.Text (isOneLine)
import qualified Test.QuickCheck.Property as QCP
import Test.QuickCheck.Property hiding (Result(..))
import Test.QuickCheck.Gen
import Test.QuickCheck.IO ()
import Test.QuickCheck.Random
import qualified Test.QuickCheck.Test as QC (showTestCount)
import Test.QuickCheck.State (State(..))
import Test.Hspec.Core.Util
liftHook
:: r
-- ^ Default result, taken if the hook decides to not run the test
-> ((a -> IO ()) -> IO ())
-- ^ Hook: given an action to actually run the test, potentially does
-- some IO actions before the test, runs it while supplying an argument (or
-- skips it!), then may execute some more actions after running it.
-> (a -> IO r)
-- ^ The actual test being run, producing a result
-> IO r
liftHook def hook inner = do
ref <- newIORef def
hook $ inner >=> writeIORef ref
readIORef ref
aroundProperty :: ((a -> IO ()) -> IO ()) -> (a -> Property) -> Property
aroundProperty hook p = MkProperty . MkGen $ \r n -> aroundProp hook $ \a -> (unGen . unProperty $ p a) r n
aroundProp :: ((a -> IO ()) -> IO ()) -> (a -> Prop) -> Prop
aroundProp hook p = MkProp $ aroundRose hook (\a -> unProp $ p a)
aroundRose :: ((a -> IO ()) -> IO ()) -> (a -> Rose QCP.Result) -> Rose QCP.Result
aroundRose hook r = ioRose $ do
liftHook (return QCP.succeeded) hook $ \ a -> reduceRose (r a)
newSeed :: IO Int
newSeed = fst . randomR (0, fromIntegral (maxBound :: Int32)) <$>
newQCGen
mkGen :: Int -> QCGen
mkGen = mkQCGen
formatNumbers :: Int -> Int -> String
formatNumbers n shrinks = "(after " ++ pluralize n "test" ++ shrinks_ ++ ")"
where
shrinks_
| shrinks > 0 = " and " ++ pluralize shrinks "shrink"
| otherwise = ""
data QuickCheckResult = QuickCheckResult {
quickCheckResultNumTests :: Int
, quickCheckResultInfo :: String
, quickCheckResultStatus :: Status
} deriving Show
data Status =
QuickCheckSuccess
| QuickCheckFailure QuickCheckFailure
| QuickCheckOtherFailure String
deriving Show
data QuickCheckFailure = QCFailure {
quickCheckFailureNumShrinks :: Int
, quickCheckFailureException :: Maybe SomeException
, quickCheckFailureReason :: String
, quickCheckFailureCounterexample :: [String]
} deriving Show
parseQuickCheckResult :: Result -> QuickCheckResult
parseQuickCheckResult r = case r of
Success {..} -> result output QuickCheckSuccess
Failure {..} ->
case stripSuffix outputWithoutVerbose output of
Just xs -> result verboseOutput (QuickCheckFailure $ QCFailure numShrinks theException reason failingTestCase)
where
verboseOutput
| xs == "*** Failed! " = ""
| otherwise = maybeStripSuffix "*** Failed!" (strip xs)
Nothing -> couldNotParse output
where
outputWithoutVerbose = reasonAndNumbers ++ unlines failingTestCase
reasonAndNumbers
| isOneLine reason = reason ++ " " ++ numbers ++ colonNewline
| otherwise = numbers ++ colonNewline ++ ensureTrailingNewline reason
numbers = formatNumbers numTests numShrinks
colonNewline = ":\n"
GaveUp {..} ->
case stripSuffix outputWithoutVerbose output of
Just info -> otherFailure info ("Gave up after " ++ numbers ++ "!")
Nothing -> couldNotParse output
where
numbers = showTestCount numTests numDiscarded
outputWithoutVerbose = "*** Gave up! Passed only " ++ numbers ++ " tests.\n"
NoExpectedFailure {..} -> case splitBy "*** Failed! " output of
Just (info, err) -> otherFailure info err
Nothing -> couldNotParse output
where
result = QuickCheckResult (numTests r) . strip
otherFailure info err = result info (QuickCheckOtherFailure $ strip err)
couldNotParse = result "" . QuickCheckOtherFailure
showTestCount :: Int -> Int -> String
showTestCount success discarded = QC.showTestCount state
where
state = MkState {
terminal = undefined
, maxSuccessTests = undefined
, maxDiscardedRatio = undefined
, coverageConfidence = undefined
#if MIN_VERSION_QuickCheck(2,15,0)
, maxTestSize = 0
, replayStartSize = undefined
#else
, computeSize = undefined
#endif
, numTotMaxShrinks = 0
, numSuccessTests = success
, numDiscardedTests = discarded
, numRecentlyDiscardedTests = 0
, labels = mempty
, classes = mempty
, tables = mempty
, requiredCoverage = mempty
, expected = True
, randomSeed = mkGen 0
, numSuccessShrinks = 0
, numTryShrinks = 0
, numTotTryShrinks = 0
}
ensureTrailingNewline :: String -> String
ensureTrailingNewline = unlines . lines
maybeStripPrefix :: String -> String -> String
maybeStripPrefix prefix m = fromMaybe m (stripPrefix prefix m)
maybeStripSuffix :: String -> String -> String
maybeStripSuffix suffix = reverse . maybeStripPrefix (reverse suffix) . reverse
stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]
stripSuffix suffix = fmap reverse . stripPrefix (reverse suffix) . reverse
splitBy :: String -> String -> Maybe (String, String)
splitBy sep xs = listToMaybe [
(x, y) | (x, Just y) <- zip (inits xs) (map stripSep $ tails xs)
]
where
stripSep = stripPrefix sep