    {-# OPTIONS_GHC -cpp #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Test.QuickCheck.Batch
    -- Copyright   :  (c) Andy Gill 2001
    -- License     :  BSD-style (see the file libraries/base/LICENSE)
    --
    -- Maintainer  :  libraries@haskell.org
    -- Stability   :  experimental
    -- Portability :  non-portable (uses Control.Exception, Control.Concurrent)
    --
    -- A batch driver for running QuickCheck.
    --
    -- /Note:/ in GHC only, it is possible to place a time limit on each test,
    -- to ensure that testing terminates.
    --
    -----------------------------------------------------------------------------

    {-
     - Here is the key for reading the output.
     -  . = test successful
     -  ? = every example passed, but quickcheck did not find enough good examples
     -  * = test aborted for some reason (out-of-time, bottom, etc)
     -  # = test failed outright
     -
     - We also provide the dangerous "isBottom".
     -
     - Here is is an example of use for sorting:
     -
     - testOptions :: TestOptions
     - testOptions = TestOptions
     -                 { no_of_tests = 100		-- number of tests to run
     -                 , length_of_tests = 1	-- 1 second max per check
     -						-- where a check == n tests
     -                 , debug_tests = False	-- True => debugging info
     -                 }
     -
     - prop_sort1 xs = sort xs == sortBy compare xs
     -   where types = (xs :: [OrdALPHA])
     - prop_sort2 xs =
     -         (not (null xs)) ==>
     -         (head (sort xs) == minimum xs)
     -   where types = (xs :: [OrdALPHA])
     - prop_sort3 xs = (not (null xs)) ==>
     -         last (sort xs) == maximum xs
     -   where types = (xs :: [OrdALPHA])
     - prop_sort4 xs ys =
     -         (not (null xs)) ==>
     -         (not (null ys)) ==>
     -         (head (sort (xs ++ ys)) == min (minimum xs) (minimum ys))
     -   where types = (xs :: [OrdALPHA], ys :: [OrdALPHA])
     - prop_sort6 xs ys =
     -         (not (null xs)) ==>
     -         (not (null ys)) ==>
     -         (last (sort (xs ++ ys)) == max (maximum xs) (maximum ys))
     -   where types = (xs :: [OrdALPHA], ys :: [OrdALPHA])
     - prop_sort5 xs ys =
     -         (not (null xs)) ==>
     -         (not (null ys)) ==>
     -         (head (sort (xs ++ ys)) == max (maximum xs) (maximum ys))
     -   where types = (xs :: [OrdALPHA], ys :: [OrdALPHA])
     -
     - test_sort = runTests "sort" testOptions
     -         [ run prop_sort1
     -         , run prop_sort2
     -         , run prop_sort3
     -         , run prop_sort4
     -         , run prop_sort5
     -         ]
     -
     - When run, this gives
     - Main> test_sort
     -                     sort : .....
     -
     - You would tie together all the test_* functions
     - into one test_everything, on a per module basis.
     -
     -}

    #if defined(__NHC__) && __NHC__ > 120
    #define BASE4 1
    #endif

    module Test.QuickCheck.Batch
       ( run		-- :: Testable a => a -> TestOptions -> IO TestResult
       , runTests		-- :: String -> TestOptions ->
    			--	[TestOptions -> IO TestResult] -> IO ()
       , defOpt		-- :: TestOptions
       , TestOptions (..)
       , TestResult (..)
       , isBottom		-- :: a -> Bool
       , bottom		-- :: a 		{- _|_ -}
       ) where

    import Prelude

    import System.Random
    #ifdef __GLASGOW_HASKELL__
    import Control.Concurrent
    #endif
    import Control.Exception hiding (catch, evaluate)
    #if BASE4
    import qualified Control.Exception as Exception
    #else
    import qualified Control.Exception as Exception (catch, evaluate)
    #endif
    import Test.QuickCheck
    import System.IO.Unsafe

    data TestOptions = TestOptions {
    	no_of_tests     :: Int,	-- ^ number of tests to run.
    	length_of_tests :: Int,	-- ^ time limit for test, in seconds.
    				-- If zero, no time limit.
    				-- /Note:/ only GHC supports time limits.
    	debug_tests     :: Bool }

    defOpt :: TestOptions
    defOpt = TestOptions
    	{ no_of_tests = 100
    	, length_of_tests = 1
    	, debug_tests = False
    	}

    data TestResult = TestOk 	String  Int [[String]]
    		| TestExausted 	String  Int [[String]]
    		| TestFailed   [String] Int
    #if BASE4
    		| TestAborted   SomeException
    #else
    		| TestAborted   Exception
    #endif

    tests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]]
          -> IO TestResult
    tests config gen rnd0 ntest nfail stamps
      | ntest == configMaxTest config = return (TestOk  "OK, passed" ntest stamps)
      | nfail == configMaxFail config = return (TestExausted "Arguments exhausted after"
    					 ntest stamps)
      | otherwise               =
          do (if not (null txt) then putStr txt else return ())
    	 case ok result of
               Nothing    ->
                 tests config gen rnd1 ntest (nfail+1) stamps
               Just True  ->
                 tests config gen rnd1 (ntest+1) nfail (stamp result:stamps)
               Just False ->
                 do return (TestFailed (arguments result) ntest)
         where
          txt         = configEvery config ntest (arguments result)
          result      = generate (configSize config ntest) rnd2 gen
          (rnd1,rnd2) = split rnd0

    batch n v = Config
      { configMaxTest = n
      , configMaxFail = n * 10
      , configSize    = (+ 3) . (`div` 2)
      , configEvery   = \n args -> if v then show n ++ ":\n" ++ unlines args else ""
      }

    -- | Run the test.
    -- Here we use the same random number each time,
    -- so we get reproducable results!
    run :: Testable a => a -> TestOptions -> IO TestResult
    run a TestOptions { no_of_tests = n, length_of_tests = len, debug_tests = debug } =
    #ifdef __GLASGOW_HASKELL__
      do me <- myThreadId
         ready <- newEmptyMVar
         r <- if len == 0
    	   then try theTest
    	   else try (do
         	     -- This waits a bit, then raises an exception in its parent,
                 -- saying, right, you've had long enough!
    	     watcher <- forkIO (Exception.catch
    			      (do threadDelay (len * 1000 * 1000)
    				  takeMVar ready
    				  throwTo me NonTermination
    				  return ())
    #if BASE4
                                  (\ e -> case e of
                                      Exception.ThreadKilled -> return ()
                                      _ -> throw e))
    #else
    			      (\ _ -> return ()))
    #endif
    	     -- Tell the watcher we are starting...
    	     putMVar ready ()
                 -- This is cheating, because possibly some of the internal message
                 -- inside "r" might be _|_, but anyway....
    	     r <- theTest
    	     -- Now, we turn off the watcher.
    	     -- Ignored if the watcher is already dead,
    	     -- (unless some unlucky thread picks up the same name)
    	     killThread watcher
    	     return r)
         case r of
            Right r -> return r
            Left  e -> return (TestAborted e)
    #else
         Exception.catch theTest $ \ e -> return (TestAborted e)
    #endif
      where
    	theTest = tests (batch n debug) (evaluate a) (mkStdGen 0) 0 0 []

    -- | Prints a one line summary of various tests with common theme
    runTests :: String -> TestOptions -> [TestOptions -> IO TestResult] -> IO ()
    runTests name scale actions =
      do putStr (rjustify 25 name ++ " : ")
         f <- tr 1 actions [] 0
         mapM fa f
         return ()
      where
    	rjustify n s = replicate (max 0 (n - length s)) ' ' ++ s

    	tr n [] xs c = do
    			putStr (rjustify (max 0 (35-n)) " (" ++ show c ++ ")\n")
    			return xs
    	tr n (action:actions) others c =
    	   do r <- action scale
    	      case r of
    		(TestOk _ m _)
    			-> do { putStr "." ;
    			       tr (n+1) actions others (c+m) }
    		(TestExausted s m ss)

    			-> do { putStr "?" ;
    			       tr (n+1) actions others (c+m) }
    		(TestAborted e)
    			-> do { putStr "*" ;
    			       tr (n+1) actions others c }
    	  	(TestFailed f num)
    			-> do { putStr "#" ;
    			        tr (n+1) actions ((f,n,num):others) (c+num) }

    	fa :: ([String],Int,Int) -> IO ()
    	fa (f,n,no) =
    	  do putStr "\n"
    	     putStr ("    ** test "
    			++ show (n  :: Int)
    			++ " of "
    			++ name
    			++ " failed with the binding(s)\n")
    	     sequence_ [putStr ("    **   " ++ v ++ "\n")
    			| v <- f ]
      	     putStr "\n"

    bottom :: a
    bottom = error "_|_"

    -- | Look out behind you! These can be misused badly.
    -- However, in the context of a batch tester, can also be very useful.
    --
    -- Examples of use of bottom and isBottom:
    --
    -- >	{- test for abort -}
    -- >	prop_head2 = isBottom (head [])
    -- >	{- test for strictness -}
    -- >	prop_head3 = isBottom (head bottom)

    isBottom :: a -> Bool
    isBottom a = unsafePerformIO (do
    	a' <- try (Exception.evaluate a)
    	case a' of
    #if BASE4
               Left e -> let _ = e :: SomeException -- XXX Euch, want pattern sigs
                         in return True
    #else
    	   Left _ -> return True
    #endif
    	   Right _ -> return False)