{-# LANGUAGE CPP #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Test.QuickCheck.Parallel
-- Copyright   :  (c) Don Stewart 2006
-- License     :  BSD-style (see the file LICENSE)
--
-- Maintainer  :  shelarcy <shelarcy@gmail.com>
-- Stability   :  experimental
-- Portability :  non-portable (uses Control.Exception, Control.Concurrent)
--
-- A parallel batch driver for running QuickCheck on threaded or SMP systems.
-- See the /Example.hs/ file for a complete overview.
--

module Test.QuickCheck.Parallel (
                                 module Test.QuickCheck,
                                 pRun,
                                 pRun',
                                 pRunAllProcessors,
                                 pRunWithNum,
                                 pDet,
                                 pNon ) where

import Test.QuickCheck
import Test.QuickCheck.Gen  (unGen)
import Test.QuickCheck.Test (test)
import Test.QuickCheck.Text (newNullTerminal)
import Test.QuickCheck.State

import Control.Concurrent
#if   __GLASGOW_HASKELL__ < 702
import GHC.Conc           (numCapabilities)
#elif __GLASGOW_HASKELL__ < 704
#else
import GHC.Conc           (getNumProcessors, setNumCapabilities)
#endif
import Control.Monad      (forM_, unless)
import System.Random
import System.IO          (hFlush,stdout)
import Text.Printf

type Name   = String
type Depth  = Int
type Test   = (Name, Depth -> IO String)

-- | Deprecated: Backwards-compatible API
{-# DEPRECATED pRun "use pRun' or pRunAllProcessors, pRunWithNum instead." #-}
pRun :: Int -> Int -> [Test] -> IO ()
pRun = pRunWithNum

-- | Variant of 'pRunWithNum'. Run a list of QuickCheck properties in parallel
-- chunks, using number of Haskell threads that can run truly simultaneously
-- (on separate physical processors) at any given time. (see 'getNumCapabilities'
-- for more details).
pRun'  :: Int -> [Test] -> IO ()
pRun' depth tests = do
#if __GLASGOW_HASKELL__ >= 702
    num <- getNumCapabilities
#else
    let num = numCapabilities
#endif
    pRun num depth tests

-- | Variant of 'pRunWithNum'. Run a list of QuickCheck properties in parallel
-- chunks, using all Processors.
pRunAllProcessors  :: Int -> [Test] -> IO ()
#if __GLASGOW_HASKELL__ < 704
pRunAllProcessors = pRun'
#else
pRunAllProcessors depth tests = do
    caps <- getNumCapabilities
    pros <- getNumProcessors
    unless (caps == pros)
      $ setNumCapabilities pros
    pRun pros depth tests
#endif

-- | Run a list of QuickCheck properties in parallel chunks, using
-- 'n' Haskell threads (first argument), and test to a depth of 'd'
-- (second argument). Compile your application with '-threaded' and run
-- with the SMP runtime's '-N4' (or however many OS threads you want to
-- donate), for best results.
--
-- > import Test.QuickCheck.Parallel
-- >
-- > do n <- getArgs >>= readIO . head
-- >    pRun n 1000 [ ("sort1", pDet prop_sort1) ]
--
-- Will run 'n' threads over the property list, to depth 1000.
--
pRunWithNum :: Int -> Int -> [Test] -> IO ()
pRunWithNum n depth tests = do
    chan <- newChan
    ps   <- getChanContents chan
    work <- newMVar tests

    forM_ [1..n] $ forkIO . thread work chan

    let wait xs i
            | i >= n    = return () -- done
            | otherwise = case xs of
                    Nothing : ys -> wait ys $! i+1
                    Just s  : ys -> putStr s >> hFlush stdout >> wait ys i
    wait ps 0

  where
    thread :: MVar [Test] -> Chan (Maybe String) -> Int -> IO ()
    thread work chan me = loop
      where
        loop = do
            job <- modifyMVar work $ \jobs -> return $ case jobs of
                        []     -> ([], Nothing)
                        (j:js) -> (js, Just j)
            case job of
                Nothing          -> writeChan chan Nothing -- done
                Just (name,prop) -> do
                    v <- prop depth
                    writeChan chan . Just $ printf "%d: %-25s: %s" me name v
                    loop

-- | Wrap a property, and run it on a deterministic set of data
pDet :: Testable a => a -> Int -> IO String
pDet a n =
  do result <- mycheck Det (stdArgs { maxSuccess = n }) a
     return $ output result

-- | Wrap a property, and run it on a non-deterministic set of data
pNon :: Testable a => a -> Int -> IO String
pNon a n =
  do result <- mycheck NonDet (stdArgs { maxSuccess = n }) a
     return $ output result

data Mode = Det | NonDet

------------------------------------------------------------------------

mycheck :: Testable a => Mode -> Args -> a -> IO Result
mycheck Det config a = do
     let rnd = mkStdGen 99  -- deterministic
     mytests config rnd a

mycheck NonDet config a = do
    rnd <- newStdGen        -- different each run
    mytests config rnd a

mytests :: Testable prop => Args -> StdGen -> prop -> IO Result
mytests a rnd p =
  do tm <- newNullTerminal
     test MkState{ terminal          = tm
                 , maxSuccessTests   = maxSuccess a
                 , maxDiscardedTests = maxDiscard a
                 , computeSize       = case replay a of
                                         Nothing    -> computeSize'
                                         Just (_,s) -> \_ _ -> s
                 , numSuccessTests   = 0
                 , numDiscardedTests = 0
                 , collected         = []
                 , expectedFailure   = False
                 , randomSeed        = rnd
                 , numSuccessShrinks = 0
                 , numTryShrinks     = 0
                 } (unGen (property p))
  where computeSize' n d
          -- e.g. with maxSuccess = 250, maxSize = 100, goes like this:
          -- 0, 1, 2, ..., 99, 0, 1, 2, ..., 99, 0, 2, 4, ..., 98.
          | n `roundTo` maxSize a + maxSize a <= maxSuccess a ||
            n >= maxSuccess a ||
            maxSuccess a `mod` maxSize a == 0 = n `mod` maxSize a + d `div` 10
          | otherwise =
            (n `mod` maxSize a) * maxSize a `div` (maxSuccess a `mod` maxSize a) + d `div` 10
        n `roundTo` m = (n `div` m) * m