{-# LANGUAGE RankNTypes #-} -- | Running tests. To check a 'TestTree' in a REPL, use -- 'quickCheckTree'; to check a QuickCheck 'Testable', just use -- 'Test.QuickCheck.quickCheck' and the similar functions in -- "Test.QuickCheck". The other functions in this module will be more -- useful for checking from within a compiled test program. -- -- The QuickCheck docs in "Test.QuickCheck" contain warnings about how -- type defaulting in GHCi can cause types to silently default to -- @()@; all those warnings also apply to use of the functions in this -- module. Although QuickCheck has some Template Haskell to help with -- this, Quickpull currently does not. You don't have to worry about -- this in non-interactive use, as the defaulting rules are more -- strict and, with warnings on, GHC will warn you when it defaults a -- type. module Quickpull.Runners ( -- * Testing 'TestTree' quickCheckTree , treeWithResult -- * Testing 'Decree's , decreeWithResult , seeDecree -- * Default main functions , defaultMain , defaultMainWith -- * Summarizing , summarize , exitCode ) where import Test.QuickCheck ( Testable, Result(..), quickCheckResult, quickCheckWithResult, Args ) import Quickpull.Types import Quickpull.Formatting import Quickpull.Render import Data.List (foldl') import System.Exit import Control.Applicative -- | Checks a 'TestTree' and prints the result to standard output in -- addition to returning it as a list of 'Result'. Each 'Decree' -- returns a list of 'Result' (a 'Single' returns a single 'Result', -- while a 'Multi' returns a 'Result' for each test in the tree.) treeWithResult :: (forall a. Testable a => a -> IO Result) -> TestTree -> IO [Result] treeWithResult run = go 0 where go lvl (TestTree l n) = do putStr . indent lvl $ l case n of Group tt -> fmap concat . mapM (go (succ lvl)) $ tt Test t -> do putStr (replicate (indentAmt * (succ lvl)) ' ') fmap (:[]) $ run t -- | Checks a 'TestTree' and prints the result to standard output. -- Intended for use in a REPL; however, the QuickCheck docs in -- "Test.QuickCheck" contain warnings about how type defaulting in -- GHCi can cause types to silently default to @()@; all those -- warnings also apply to use of this function. quickCheckTree :: TestTree -> IO () quickCheckTree t = do _ <- treeWithResult quickCheckResult t return () -- | Tests a 'Decree' and prints the result to standard output in -- addition to returning a list of 'Result'. Each 'Decree' returns a -- list of 'Result' (a 'Single' returns a single 'Result', while a -- 'Multi' returns a 'Result' for each test in the tree.) decreeWithResult :: (forall a. Testable a => a -> IO Result) -> Decree -> IO [Result] decreeWithResult run (Decree m i) = do putStr . metaLine $ m case i of Single a -> fmap (:[]) . run $ a Multi t -> treeWithResult run t -- | Tallies up the 'Result's. summarize :: [Result] -> Summary summarize = foldl' f (Summary 0 0 0 0) where f s r = case r of Success {} -> s { success = succ (success s) } GaveUp {} -> s { gaveUp = succ (gaveUp s) } Failure {} -> s { failure = succ (failure s) } NoExpectedFailure {} -> s { noExpectedFailure = succ (noExpectedFailure s) } -- | Exit successfully if there were no failures, give-ups, or -- no-expected-failures; otherwise, exit unsuccessfully. exitCode :: Summary -> ExitCode exitCode s | gaveUp s == 0 && failure s == 0 && noExpectedFailure s == 0 = ExitSuccess | otherwise = ExitFailure 1 -- | Tests each 'Decree' using a custom function that you specify; -- this allows you to vary the test depending on what's in the -- 'Decree'. Each 'Decree' returns a list of 'Result' (a 'Single' -- returns a single 'Result', while a 'Multi' returns a 'Result' for -- each test in the tree.) The tests are printed to standard output -- as they run, in addition to returning the 'Result'. seeDecree :: (Decree -> forall a. Testable a => a -> IO Result) -> [Decree] -> IO [[Result]] seeDecree f ds = mapM g ds where g d = decreeWithResult (f d) d <* putStrLn "" testDecrees :: (Decree -> forall a. Testable a => a -> IO Result) -> [Decree] -> IO () testDecrees f ds = do rs <- fmap concat $ seeDecree f ds let s = summarize rs c = exitCode s putStr $ summary s exitWith c -- | Tests each 'Decree' and prints the results to standard output. -- Exits successfully if all tests succeeded; otherwise, exits -- unsuccessfully. -- -- Not recommended for REPL use as this function will either kill your -- REPL when it's done or, in the case of recent GHC versions, issue -- an exception. defaultMain :: [Decree] -> IO () defaultMain = testDecrees (const quickCheckResult) -- | Like 'defaultMain' but allows you to pass arguments to the -- QuickCheck driver. -- -- Not recommended for REPL use as this function will either kill your -- REPL when it's done or, in the case of recent GHC versions, issue -- an exception. defaultMainWith :: Args -> [Decree] -> IO () defaultMainWith a = testDecrees (const (quickCheckWithResult a))