-- vim:fdm=marker
{-# LANGUAGE BangPatterns, ImplicitParams, MultiParamTypeClasses, DeriveDataTypeable, FlexibleContexts, CApiFFI, NamedFieldPuns #-}
-- | Console reporter ingredient.
--
-- @since 0.11.3
module Test.Tasty.Ingredients.ConsoleReporter
  ( consoleTestReporter
  , consoleTestReporterWithHook
  , Quiet(..)
  , HideSuccesses(..)
  , MinDurationToReport(..)
  , AnsiTricks(..)
  -- * Internals
  -- | The following functions and datatypes are internals that are exposed to
  -- simplify the task of rolling your own custom console reporter UI.

  -- ** Output colouring
  , UseColor(..)
  , useColor
  -- ** Test failure statistics
  , Statistics(..)
  , computeStatistics
  , printStatistics
  , printStatisticsNoTime
  -- ** Outputting results
  , TestOutput(..)
  , buildTestOutput
  , foldTestOutput
  , withConsoleFormat
  ) where

import Prelude hiding (fail, EQ)
import Control.Monad (join, unless, void, when, (<=<))
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Reader (Reader, runReader, ask)
import Control.Monad.Trans.State (evalState, evalStateT, get, modify, put)
import Control.Concurrent.STM
import Control.Exception
import Test.Tasty.Core
import Test.Tasty.Providers.ConsoleFormat
import Test.Tasty.Run
import Test.Tasty.Ingredients
import Test.Tasty.Ingredients.ListTests
import Test.Tasty.Options
import Test.Tasty.Options.Core
import Test.Tasty.Patterns
import Test.Tasty.Patterns.Printer
import Test.Tasty.Patterns.Types
import Test.Tasty.Runners.Reducers
import Test.Tasty.Runners.Utils
import Text.Printf
import qualified Data.IntMap as IntMap
import Data.Char
#ifdef USE_WCWIDTH
import Foreign.C.Types (CInt(..), CWchar(..))
#endif
import Data.List (isInfixOf)
import Data.Maybe
import Data.Monoid (Any(..))
import qualified Data.Semigroup as Sem
import Data.Typeable
import Options.Applicative hiding (action, str, Success, Failure)
import System.IO
import System.Console.ANSI
#if !MIN_VERSION_base(4,11,0)
import Data.Foldable (foldMap)
#endif

--------------------------------------------------
-- TestOutput base definitions
--------------------------------------------------
-- {{{
-- | 'TestOutput' is an intermediary between output formatting and output
-- printing. It lets us have several different printing modes (normal; print
-- failures only; quiet).
--
-- @since 0.12
data TestOutput
  = -- | Printing a test.
    PrintTest
      String
        -- ^ Name of the test.
      (IO ())
        -- ^ Action that prints the test name.
      (Progress -> IO ())
        -- ^ Action that prints the progress of the test.  /Since: 1.5/
      (Result -> IO ())
        -- ^ Action that renders the result of the test.
  | -- | Printing a test group.
    PrintHeading
      String
        -- ^ Name of the test group
      (IO ())
        -- ^ Action that prints the heading of a test group.
      TestOutput
        -- ^ The 'TestOutput' for that test group.
  | -- | Inactive test (e.g. not matching the current pattern).
    Skip
  | -- | Two sets of 'TestOutput' on the same level.
    Seq TestOutput TestOutput

-- The monoid laws should hold observationally w.r.t. the semantics defined
-- in this module.
--
-- @since 0.12.0.1
instance Sem.Semigroup TestOutput where
  <> :: TestOutput -> TestOutput -> TestOutput
(<>) = TestOutput -> TestOutput -> TestOutput
Seq
instance Monoid TestOutput where
  mempty :: TestOutput
mempty = TestOutput
Skip
#if !MIN_VERSION_base(4,11,0)
  mappend = (Sem.<>)
#endif

applyHook :: ([TestName] -> Result -> IO Result) -> TestOutput -> TestOutput
applyHook :: ([TestName] -> Result -> IO Result) -> TestOutput -> TestOutput
applyHook [TestName] -> Result -> IO Result
hook = [TestName] -> TestOutput -> TestOutput
go []
  where
    go :: [TestName] -> TestOutput -> TestOutput
go [TestName]
path (PrintTest TestName
name IO ()
printName Progress -> IO ()
printProgress Result -> IO ()
printResult) =
      TestName
-> IO () -> (Progress -> IO ()) -> (Result -> IO ()) -> TestOutput
PrintTest TestName
name IO ()
printName Progress -> IO ()
printProgress (Result -> IO ()
printResult forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< [TestName] -> Result -> IO Result
hook (TestName
name forall a. a -> [a] -> [a]
: [TestName]
path))
    go [TestName]
path (PrintHeading TestName
name IO ()
printName TestOutput
printBody) =
      TestName -> IO () -> TestOutput -> TestOutput
PrintHeading TestName
name IO ()
printName ([TestName] -> TestOutput -> TestOutput
go (TestName
name forall a. a -> [a] -> [a]
: [TestName]
path) TestOutput
printBody)
    go [TestName]
path (Seq TestOutput
a TestOutput
b) = TestOutput -> TestOutput -> TestOutput
Seq ([TestName] -> TestOutput -> TestOutput
go [TestName]
path TestOutput
a) ([TestName] -> TestOutput -> TestOutput
go [TestName]
path TestOutput
b)
    go [TestName]
_ TestOutput
Skip = forall a. Monoid a => a
mempty

type Level = Int

-- | Build the 'TestOutput' for a 'TestTree' and 'OptionSet'. The @colors@
-- ImplicitParam controls whether the output is colored.
--
-- @since 0.11.3
buildTestOutput :: (?colors :: Bool) => OptionSet -> TestTree -> TestOutput
buildTestOutput :: (?colors::Bool) => OptionSet -> TestTree -> TestOutput
buildTestOutput OptionSet
opts TestTree
tree =
  let
    -- Do not retain the reference to the tree more than necessary
    !alignment :: Int
alignment = OptionSet -> TestTree -> Int
computeAlignment OptionSet
opts TestTree
tree

    MinDurationToReport{Integer
minDurationMicros :: MinDurationToReport -> Integer
minDurationMicros :: Integer
minDurationMicros} = forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    AnsiTricks{Bool
getAnsiTricks :: AnsiTricks -> Bool
getAnsiTricks :: Bool
getAnsiTricks} = forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts

    runSingleTest
      :: (IsTest t, ?colors :: Bool)
      => OptionSet -> TestName -> t -> Ap (Reader Level) TestOutput
    runSingleTest :: forall t.
(IsTest t, ?colors::Bool) =>
OptionSet -> TestName -> t -> Ap (ReaderT Int Identity) TestOutput
runSingleTest OptionSet
_opts TestName
name t
_test = forall (f :: * -> *) a. f a -> Ap f a
Ap forall a b. (a -> b) -> a -> b
$ do
      Int
level <- forall (m :: * -> *) r. Monad m => ReaderT r m r
ask

      let
        indentedNameWidth :: Int
indentedNameWidth = Int
indentSize forall a. Num a => a -> a -> a
* Int
level forall a. Num a => a -> a -> a
+ TestName -> Int
stringWidth TestName
name
        postNamePadding :: Int
postNamePadding = Int
alignment forall a. Num a => a -> a -> a
- Int
indentedNameWidth

        testNamePadded :: TestName
testNamePadded = forall r. PrintfType r => TestName -> r
printf TestName
"%s%s: %s"
          (Int -> TestName
indent Int
level)
          TestName
name
          (forall a. Int -> a -> [a]
replicate Int
postNamePadding Char
' ')

        resultPosition :: Int
resultPosition = forall (t :: * -> *) a. Foldable t => t a -> Int
length TestName
testNamePadded

        printTestName :: IO ()
printTestName = do
          TestName -> IO ()
putStr TestName
testNamePadded
          Handle -> IO ()
hFlush Handle
stdout

        printTestProgress :: Progress -> IO ()
printTestProgress Progress
progress
          -- We cannot display progress properly if a terminal
          -- does not support manipulations with cursor position.
          | Bool -> Bool
not Bool
getAnsiTricks = forall (f :: * -> *) a. Applicative f => a -> f a
pure ()

          | Progress
progress forall a. Eq a => a -> a -> Bool
== Progress
emptyProgress = forall (f :: * -> *) a. Applicative f => a -> f a
pure ()

          | Bool
otherwise = do
              let
                msg :: TestName
msg = case (TestName -> TestName
cleanupProgressText forall a b. (a -> b) -> a -> b
$ Progress -> TestName
progressText Progress
progress, Float
100 forall a. Num a => a -> a -> a
* Progress -> Float
progressPercent Progress
progress) of
                        (TestName
"",  Float
pct) -> forall r. PrintfType r => TestName -> r
printf TestName
"%.0f%% " Float
pct
                        (TestName
txt, Float
0.0) -> forall r. PrintfType r => TestName -> r
printf TestName
"%s" TestName
txt
                        (TestName
txt, Float
pct) -> forall r. PrintfType r => TestName -> r
printf TestName
"%s: %.0f%% " TestName
txt Float
pct
              Int -> IO ()
setCursorColumn Int
resultPosition
              (?colors::Bool) => TestName -> IO ()
infoOk TestName
msg
              -- A new progress message may be shorter than the previous one
              -- so we must clean until the end of the line
              IO ()
clearFromCursorToLineEnd
              Handle -> IO ()
hFlush Handle
stdout

        printTestResult :: Result -> IO ()
printTestResult Result
result = do
          TestName
rDesc <- TestName -> IO TestName
formatMessage forall a b. (a -> b) -> a -> b
$ Result -> TestName
resultDescription Result
result

          -- use an appropriate printing function
          let
            printFn :: TestName -> IO ()
printFn =
              case Result -> Outcome
resultOutcome Result
result of
                Outcome
Success -> (?colors::Bool) => TestName -> IO ()
ok
                Failure FailureReason
TestDepFailed -> (?colors::Bool) => TestName -> IO ()
skipped
                Outcome
_ -> (?colors::Bool) => TestName -> IO ()
fail
            time :: Time
time = Result -> Time
resultTime Result
result

          forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
getAnsiTricks forall a b. (a -> b) -> a -> b
$ do
            Int -> IO ()
setCursorColumn Int
resultPosition
            IO ()
clearFromCursorToLineEnd

          TestName -> IO ()
printFn (Result -> TestName
resultShortDescription Result
result)
          forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall a b. (RealFrac a, Integral b) => a -> b
floor (Time
time forall a. Num a => a -> a -> a
* Time
1e6) forall a. Ord a => a -> a -> Bool
>= Integer
minDurationMicros) forall a b. (a -> b) -> a -> b
$
            TestName -> IO ()
printFn (forall r. PrintfType r => TestName -> r
printf TestName
" (%.2fs)" Time
time)
          TestName -> IO ()
printFn TestName
"\n"

          forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t a -> Bool
null TestName
rDesc) forall a b. (a -> b) -> a -> b
$
            (if Result -> Bool
resultSuccessful Result
result then (?colors::Bool) => TestName -> IO ()
infoOk else (?colors::Bool) => TestName -> IO ()
infoFail) forall a b. (a -> b) -> a -> b
$
              forall r. PrintfType r => TestName -> r
printf TestName
"%s%s\n" (Int -> TestName
indent forall a b. (a -> b) -> a -> b
$ Int
level forall a. Num a => a -> a -> a
+ Int
1) (Int -> TestName -> TestName
formatDesc (Int
levelforall a. Num a => a -> a -> a
+Int
1) TestName
rDesc)
          case Result -> ResultDetailsPrinter
resultDetailsPrinter Result
result of
            ResultDetailsPrinter Int -> ConsoleFormatPrinter -> IO ()
action -> Int -> ConsoleFormatPrinter -> IO ()
action Int
level (?colors::Bool) => ConsoleFormatPrinter
withConsoleFormat

      forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ TestName
-> IO () -> (Progress -> IO ()) -> (Result -> IO ()) -> TestOutput
PrintTest TestName
name IO ()
printTestName (?colors::Bool) => Progress -> IO ()
printTestProgress (?colors::Bool) => Result -> IO ()
printTestResult

    runGroup :: OptionSet -> TestName -> [Ap (Reader Level) TestOutput] -> Ap (Reader Level) TestOutput
    runGroup :: OptionSet
-> TestName
-> [Ap (ReaderT Int Identity) TestOutput]
-> Ap (ReaderT Int Identity) TestOutput
runGroup OptionSet
_opts TestName
name [Ap (ReaderT Int Identity) TestOutput]
grp = forall (f :: * -> *) a. f a -> Ap f a
Ap forall a b. (a -> b) -> a -> b
$ do
      Int
level <- forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
      let
        printHeading :: IO ()
printHeading = forall r. PrintfType r => TestName -> r
printf TestName
"%s%s\n" (Int -> TestName
indent Int
level) TestName
name
        printBody :: TestOutput
printBody = forall r a. Reader r a -> r -> a
runReader (forall (f :: * -> *) a. Ap f a -> f a
getApp (forall a. Monoid a => [a] -> a
mconcat [Ap (ReaderT Int Identity) TestOutput]
grp)) (Int
level forall a. Num a => a -> a -> a
+ Int
1)
      forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ TestName -> IO () -> TestOutput -> TestOutput
PrintHeading TestName
name IO ()
printHeading TestOutput
printBody

  in
    forall a b c. (a -> b -> c) -> b -> a -> c
flip forall r a. Reader r a -> r -> a
runReader Int
0 forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. Ap f a -> f a
getApp forall a b. (a -> b) -> a -> b
$
      forall b. Monoid b => TreeFold b -> OptionSet -> TestTree -> b
foldTestTree
        forall b. Monoid b => TreeFold b
trivialFold
          { foldSingle :: forall t.
IsTest t =>
OptionSet -> TestName -> t -> Ap (ReaderT Int Identity) TestOutput
foldSingle = forall t.
(IsTest t, ?colors::Bool) =>
OptionSet -> TestName -> t -> Ap (ReaderT Int Identity) TestOutput
runSingleTest
          , foldGroup :: OptionSet
-> TestName
-> [Ap (ReaderT Int Identity) TestOutput]
-> Ap (ReaderT Int Identity) TestOutput
foldGroup = OptionSet
-> TestName
-> [Ap (ReaderT Int Identity) TestOutput]
-> Ap (ReaderT Int Identity) TestOutput
runGroup
          }
          OptionSet
opts TestTree
tree

-- | Make sure the progress text does not contain any newlines or line feeds,
-- lest our ANSI magic breaks. Since the progress text is expected to be short,
-- we simply drop anything after a newline.
cleanupProgressText :: String -> String
cleanupProgressText :: TestName -> TestName
cleanupProgressText = forall a b. (a -> b) -> [a] -> [b]
map (\Char
c -> if Char -> Bool
isSpace Char
c then Char
' ' else Char
c)
                    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
takeWhile (\Char
c -> Char
c forall a. Eq a => a -> a -> Bool
/= Char
'\n' Bool -> Bool -> Bool
&& Char
c forall a. Eq a => a -> a -> Bool
/= Char
'\r' Bool -> Bool -> Bool
&& Char
c forall a. Eq a => a -> a -> Bool
/= Char
'\t')

-- | Fold function for the 'TestOutput' tree into a 'Monoid'.
--
-- @since 0.12
foldTestOutput
  :: Monoid b
  => (String -> IO () -> IO Result -> (Result -> IO ()) -> b)
  -- ^ Eliminator for test cases. The @IO ()@ prints the testname. The
  -- @IO Result@ blocks until the test is finished, returning it's 'Result'.
  -- The @Result -> IO ()@ function prints the formatted output.
  -> (String -> IO () -> b -> b)
  -- ^ Eliminator for test groups. The @IO ()@ prints the test group's name.
  -- The @b@ is the result of folding the test group.
  -> TestOutput -- ^ The @TestOutput@ being rendered.
  -> StatusMap -- ^ The @StatusMap@ received by the 'TestReporter'
  -> b
foldTestOutput :: forall b.
Monoid b =>
(TestName -> IO () -> IO Result -> (Result -> IO ()) -> b)
-> (TestName -> IO () -> b -> b) -> TestOutput -> StatusMap -> b
foldTestOutput TestName -> IO () -> IO Result -> (Result -> IO ()) -> b
foldTest TestName -> IO () -> b -> b
foldHeading TestOutput
outputTree StatusMap
smap =
  forall a b c. (a -> b -> c) -> b -> a -> c
flip forall s a. State s a -> s -> a
evalState Int
0 forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. Ap f a -> f a
getApp forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}. Monad m => TestOutput -> Ap (StateT Int m) b
go TestOutput
outputTree where

  go :: TestOutput -> Ap (StateT Int m) b
go (PrintTest TestName
name IO ()
printName Progress -> IO ()
printProgress Result -> IO ()
printResult) = forall (f :: * -> *) a. f a -> Ap f a
Ap forall a b. (a -> b) -> a -> b
$ do
    Int
ix <- forall (m :: * -> *) s. Monad m => StateT s m s
get
    forall (m :: * -> *) s. Monad m => s -> StateT s m ()
put forall a b. (a -> b) -> a -> b
$! Int
ix forall a. Num a => a -> a -> a
+ Int
1
    let
      statusVar :: TVar Status
statusVar =
        forall a. a -> Maybe a -> a
fromMaybe (forall a. HasCallStack => TestName -> a
error TestName
"internal error: index out of bounds") forall a b. (a -> b) -> a -> b
$
        forall a. Int -> IntMap a -> Maybe a
IntMap.lookup Int
ix StatusMap
smap

    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ TestName -> IO () -> IO Result -> (Result -> IO ()) -> b
foldTest TestName
name IO ()
printName (TVar Status -> (Progress -> IO ()) -> IO Result
ppProgressOrResult TVar Status
statusVar Progress -> IO ()
printProgress) Result -> IO ()
printResult

  go (PrintHeading TestName
name IO ()
printName TestOutput
printBody) = forall (f :: * -> *) a. f a -> Ap f a
Ap forall a b. (a -> b) -> a -> b
$
    TestName -> IO () -> b -> b
foldHeading TestName
name IO ()
printName forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (f :: * -> *) a. Ap f a -> f a
getApp (TestOutput -> Ap (StateT Int m) b
go TestOutput
printBody)
  go (Seq TestOutput
a TestOutput
b) = forall a. Monoid a => a -> a -> a
mappend (TestOutput -> Ap (StateT Int m) b
go TestOutput
a) (TestOutput -> Ap (StateT Int m) b
go TestOutput
b)
  go TestOutput
Skip = forall a. Monoid a => a
mempty

-- }}}

--------------------------------------------------
-- TestOutput modes
--------------------------------------------------

ppProgressOrResult :: TVar Status -> (Progress -> IO ()) -> IO Result
ppProgressOrResult :: TVar Status -> (Progress -> IO ()) -> IO Result
ppProgressOrResult TVar Status
statusVar Progress -> IO ()
ppProgress = Progress -> IO Result
go Progress
emptyProgress where
  go :: Progress -> IO Result
go Progress
old_p = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (\Progress
p -> Progress -> IO ()
ppProgress Progress
p forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Progress -> IO Result
go Progress
p) forall (m :: * -> *) a. Monad m => a -> m a
return forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
    Status
status <- forall a. TVar a -> STM a
readTVar TVar Status
statusVar
    case Status
status of
      Executing Progress
p
        | Progress
p forall a. Eq a => a -> a -> Bool
== Progress
old_p -> forall a. STM a
retry
        | Bool
otherwise -> forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a b. a -> Either a b
Left Progress
p
      Done Result
r      -> forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a b. b -> Either a b
Right Result
r
      Status
_           -> forall a. STM a
retry
    )

-- {{{
consoleOutput :: (?colors :: Bool) => TestOutput -> StatusMap -> IO ()
consoleOutput :: (?colors::Bool) => TestOutput -> StatusMap -> IO ()
consoleOutput TestOutput
toutput StatusMap
smap =
  forall (f :: * -> *). Traversal f -> f ()
getTraversal forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst forall a b. (a -> b) -> a -> b
$ forall b.
Monoid b =>
(TestName -> IO () -> IO Result -> (Result -> IO ()) -> b)
-> (TestName -> IO () -> b -> b) -> TestOutput -> StatusMap -> b
foldTestOutput forall {p} {t}.
p -> IO () -> IO t -> (t -> IO ()) -> (Traversal IO, Any)
foldTest forall {f :: * -> *} {p}.
Monad f =>
p -> f () -> (Traversal f, Any) -> (Traversal f, Any)
foldHeading TestOutput
toutput StatusMap
smap
  where
    foldTest :: p -> IO () -> IO t -> (t -> IO ()) -> (Traversal IO, Any)
foldTest p
_name IO ()
printName IO t
getResult t -> IO ()
printResult =
      ( forall (f :: * -> *). f () -> Traversal f
Traversal forall a b. (a -> b) -> a -> b
$ do
          IO ()
printName :: IO ()
          t
r <- IO t
getResult
          t -> IO ()
printResult t
r
      , Bool -> Any
Any Bool
True)
    foldHeading :: p -> f () -> (Traversal f, Any) -> (Traversal f, Any)
foldHeading p
_name f ()
printHeading (Traversal f
printBody, Any Bool
nonempty) =
      ( forall (f :: * -> *). f () -> Traversal f
Traversal forall a b. (a -> b) -> a -> b
$ do
          forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
nonempty forall a b. (a -> b) -> a -> b
$ f ()
printHeading
          forall (f :: * -> *). Traversal f -> f ()
getTraversal Traversal f
printBody
      , Bool -> Any
Any Bool
nonempty
      )

consoleOutputHidingSuccesses :: (?colors :: Bool) => TestOutput -> StatusMap -> IO ()
consoleOutputHidingSuccesses :: (?colors::Bool) => TestOutput -> StatusMap -> IO ()
consoleOutputHidingSuccesses TestOutput
toutput StatusMap
smap =
  forall (f :: * -> *) a. Functor f => f a -> f ()
void forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a. Ap f a -> f a
getApp forall a b. (a -> b) -> a -> b
$ forall b.
Monoid b =>
(TestName -> IO () -> IO Result -> (Result -> IO ()) -> b)
-> (TestName -> IO () -> b -> b) -> TestOutput -> StatusMap -> b
foldTestOutput forall {p}.
p -> IO () -> IO Result -> (Result -> IO ()) -> Ap IO Any
foldTest forall {p}. p -> IO () -> Ap IO Any -> Ap IO Any
foldHeading TestOutput
toutput StatusMap
smap
  where
    foldTest :: p -> IO () -> IO Result -> (Result -> IO ()) -> Ap IO Any
foldTest p
_name IO ()
printName IO Result
getResult Result -> IO ()
printResult =
      forall (f :: * -> *) a. f a -> Ap f a
Ap forall a b. (a -> b) -> a -> b
$ do
          IO ()
printName :: IO ()
          Result
r <- IO Result
getResult
          if Result -> Bool
resultSuccessful Result
r
            then do IO ()
clearThisLine; forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> Any
Any Bool
False
            else do Result -> IO ()
printResult Result
r :: IO (); forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> Any
Any Bool
True

    foldHeading :: p -> IO () -> Ap IO Any -> Ap IO Any
foldHeading p
_name IO ()
printHeading Ap IO Any
printBody =
      forall (f :: * -> *) a. f a -> Ap f a
Ap forall a b. (a -> b) -> a -> b
$ do
        IO ()
printHeading :: IO ()
        Any Bool
failed <- forall (f :: * -> *) a. Ap f a -> f a
getApp Ap IO Any
printBody
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
failed IO ()
clearAboveLine
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> Any
Any Bool
failed

    clearAboveLine :: IO ()
clearAboveLine = do Int -> IO ()
cursorUpLine Int
1; IO ()
clearThisLine
    clearThisLine :: IO ()
clearThisLine = do IO ()
clearLine; Int -> IO ()
setCursorColumn Int
0

streamOutputHidingSuccesses :: (?colors :: Bool) => TestOutput -> StatusMap -> IO ()
streamOutputHidingSuccesses :: (?colors::Bool) => TestOutput -> StatusMap -> IO ()
streamOutputHidingSuccesses TestOutput
toutput StatusMap
smap =
  forall (f :: * -> *) a. Functor f => f a -> f ()
void forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b c. (a -> b -> c) -> b -> a -> c
flip forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m a
evalStateT [] forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a. Ap f a -> f a
getApp forall a b. (a -> b) -> a -> b
$
    forall b.
Monoid b =>
(TestName -> IO () -> IO Result -> (Result -> IO ()) -> b)
-> (TestName -> IO () -> b -> b) -> TestOutput -> StatusMap -> b
foldTestOutput forall {m :: * -> *} {p} {a}.
MonadIO m =>
p
-> IO ()
-> IO Result
-> (Result -> IO ())
-> Ap (StateT [IO a] m) Any
foldTest forall {m :: * -> *} {p} {a}.
Monad m =>
p -> a -> Ap (StateT [a] m) Any -> Ap (StateT [a] m) Any
foldHeading TestOutput
toutput StatusMap
smap
  where
    foldTest :: p
-> IO ()
-> IO Result
-> (Result -> IO ())
-> Ap (StateT [IO a] m) Any
foldTest p
_name IO ()
printName IO Result
getResult Result -> IO ()
printResult =
      forall (f :: * -> *) a. f a -> Ap f a
Ap forall a b. (a -> b) -> a -> b
$ do
          Result
r <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ IO Result
getResult
          if Result -> Bool
resultSuccessful Result
r
            then forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> Any
Any Bool
False
            else do
              [IO a]
stack <- forall (m :: * -> *) s. Monad m => StateT s m s
get
              forall (m :: * -> *) s. Monad m => s -> StateT s m ()
put []

              forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ do
                forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ forall a. [a] -> [a]
reverse [IO a]
stack
                IO ()
printName :: IO ()
                Result -> IO ()
printResult Result
r :: IO ()

              forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> Any
Any Bool
True

    foldHeading :: p -> a -> Ap (StateT [a] m) Any -> Ap (StateT [a] m) Any
foldHeading p
_name a
printHeading Ap (StateT [a] m) Any
printBody =
      forall (f :: * -> *) a. f a -> Ap f a
Ap forall a b. (a -> b) -> a -> b
$ do
        forall (m :: * -> *) s. Monad m => (s -> s) -> StateT s m ()
modify (a
printHeading forall a. a -> [a] -> [a]
:)
        Any Bool
failed <- forall (f :: * -> *) a. Ap f a -> f a
getApp Ap (StateT [a] m) Any
printBody
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
failed forall a b. (a -> b) -> a -> b
$
          forall (m :: * -> *) s. Monad m => (s -> s) -> StateT s m ()
modify forall a b. (a -> b) -> a -> b
$ \[a]
stack ->
            case [a]
stack of
              a
_:[a]
rest -> [a]
rest
              [] -> [] -- shouldn't happen anyway
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> Any
Any Bool
failed

-- }}}

--------------------------------------------------
-- Statistics
--------------------------------------------------
-- {{{

-- | Track the number of tests that were run and failures of a 'TestTree' or
-- sub-tree.
--
-- @since 0.11.3
data Statistics = Statistics
  { Statistics -> Int
statTotal :: !Int -- ^ Number of active tests (e.g., that match the
                      -- pattern specified on the commandline), inactive tests
                      -- are not counted.
  , Statistics -> Int
statFailures :: !Int -- ^ Number of active tests that failed.
  }

-- | @since 0.12.0.1
instance Sem.Semigroup Statistics where
  Statistics Int
t1 Int
f1 <> :: Statistics -> Statistics -> Statistics
<> Statistics Int
t2 Int
f2 = Int -> Int -> Statistics
Statistics (Int
t1 forall a. Num a => a -> a -> a
+ Int
t2) (Int
f1 forall a. Num a => a -> a -> a
+ Int
f2)
instance Monoid Statistics where
  mempty :: Statistics
mempty = Int -> Int -> Statistics
Statistics Int
0 Int
0
#if !MIN_VERSION_base(4,11,0)
  mappend = (Sem.<>)
#endif

-- | @computeStatistics@ computes a summary 'Statistics' for
-- a given state of the 'StatusMap'.
-- Useful in combination with 'printStatistics'.
--
-- @since 1.2.3
computeStatistics :: StatusMap -> IO Statistics
computeStatistics :: StatusMap -> IO Statistics
computeStatistics = forall (f :: * -> *) a. Ap f a -> f a
getApp forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap (\TVar Status
var -> forall (f :: * -> *) a. f a -> Ap f a
Ap forall a b. (a -> b) -> a -> b
$
  (\Result
r -> Int -> Int -> Statistics
Statistics Int
1 (if Result -> Bool
resultSuccessful Result
r then Int
0 else Int
1))
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar Status -> IO Result
getResultFromTVar TVar Status
var)

reportStatistics :: (?colors :: Bool) => Statistics -> IO ()
reportStatistics :: (?colors::Bool) => Statistics -> IO ()
reportStatistics Statistics
st = case Statistics -> Int
statFailures Statistics
st of
    Int
0 -> (?colors::Bool) => TestName -> IO ()
ok forall a b. (a -> b) -> a -> b
$ forall r. PrintfType r => TestName -> r
printf TestName
"All %d tests passed" (Statistics -> Int
statTotal Statistics
st)
    Int
fs -> (?colors::Bool) => TestName -> IO ()
fail forall a b. (a -> b) -> a -> b
$ forall r. PrintfType r => TestName -> r
printf TestName
"%d out of %d tests failed" Int
fs (Statistics -> Int
statTotal Statistics
st)

-- | @printStatistics@ reports test success/failure statistics and time it took
-- to run. The 'Time' results is intended to be filled in by the 'TestReporter'
-- callback. The @colors@ ImplicitParam controls whether coloured output is
-- used.
--
-- @since 0.11.3
printStatistics :: (?colors :: Bool) => Statistics -> Time -> IO ()
printStatistics :: (?colors::Bool) => Statistics -> Time -> IO ()
printStatistics Statistics
st Time
time = do
  forall r. PrintfType r => TestName -> r
printf TestName
"\n"
  (?colors::Bool) => Statistics -> IO ()
reportStatistics Statistics
st
  case Statistics -> Int
statFailures Statistics
st of
    Int
0 -> (?colors::Bool) => TestName -> IO ()
ok forall a b. (a -> b) -> a -> b
$ forall r. PrintfType r => TestName -> r
printf TestName
" (%.2fs)\n" Time
time
    Int
_ -> (?colors::Bool) => TestName -> IO ()
fail forall a b. (a -> b) -> a -> b
$ forall r. PrintfType r => TestName -> r
printf TestName
" (%.2fs)\n" Time
time

-- | @printStatisticsNoTime@ reports test success/failure statistics
-- The @colors@ ImplicitParam controls whether coloured output is used.
--
-- @since 0.12
printStatisticsNoTime :: (?colors :: Bool) => Statistics -> IO ()
printStatisticsNoTime :: (?colors::Bool) => Statistics -> IO ()
printStatisticsNoTime Statistics
st = (?colors::Bool) => Statistics -> IO ()
reportStatistics Statistics
st forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall r. PrintfType r => TestName -> r
printf TestName
"\n"

-- | Wait until
--
-- * all tests have finished successfully, and return 'True', or
--
-- * at least one test has failed, and return 'False'
statusMapResult
  :: Int -- ^ lookahead
  -> StatusMap
  -> IO Bool
statusMapResult :: Int -> StatusMap -> IO Bool
statusMapResult Int
lookahead0 StatusMap
smap
  | forall a. IntMap a -> Bool
IntMap.null StatusMap
smap = forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
  | Bool
otherwise =
      forall (m :: * -> *) a. Monad m => m (m a) -> m a
join forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$
        forall a b. (Int -> a -> b -> b) -> b -> IntMap a -> b
IntMap.foldrWithKey Int
-> TVar Status
-> (IntMap () -> Int -> STM (IO Bool))
-> IntMap ()
-> Int
-> STM (IO Bool)
f IntMap () -> Int -> STM (IO Bool)
finish StatusMap
smap forall a. Monoid a => a
mempty Int
lookahead0
  where
    f :: Int
      -> TVar Status
      -> (IntMap.IntMap () -> Int -> STM (IO Bool))
      -> (IntMap.IntMap () -> Int -> STM (IO Bool))
    -- ok_tests is a set of tests that completed successfully
    -- lookahead is the number of unfinished tests that we are allowed to
    -- look at
    f :: Int
-> TVar Status
-> (IntMap () -> Int -> STM (IO Bool))
-> IntMap ()
-> Int
-> STM (IO Bool)
f Int
key TVar Status
tvar IntMap () -> Int -> STM (IO Bool)
k IntMap ()
ok_tests Int
lookahead
      | Int
lookahead forall a. Ord a => a -> a -> Bool
<= Int
0 =
          -- We looked at too many unfinished tests.
          IntMap () -> STM (IO Bool)
next_iter IntMap ()
ok_tests
      | Bool
otherwise = do
          Status
this_status <- forall a. TVar a -> STM a
readTVar TVar Status
tvar
          case Status
this_status of
            Done Result
r ->
              if Result -> Bool
resultSuccessful Result
r
                then IntMap () -> Int -> STM (IO Bool)
k (forall a. Int -> a -> IntMap a -> IntMap a
IntMap.insert Int
key () IntMap ()
ok_tests) Int
lookahead
                else forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            Status
_ -> IntMap () -> Int -> STM (IO Bool)
k IntMap ()
ok_tests (Int
lookaheadforall a. Num a => a -> a -> a
-Int
1)

    -- next_iter is called when we end the current iteration,
    -- either because we reached the end of the test tree
    -- or because we exhausted the lookahead
    next_iter :: IntMap.IntMap () -> STM (IO Bool)
    next_iter :: IntMap () -> STM (IO Bool)
next_iter IntMap ()
ok_tests =
      -- If we made no progress at all, wait until at least some tests
      -- complete.
      -- Otherwise, reduce the set of tests we are looking at.
      if forall a. IntMap a -> Bool
IntMap.null IntMap ()
ok_tests
        then forall a. STM a
retry
        else forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Int -> StatusMap -> IO Bool
statusMapResult Int
lookahead0 (forall a b. IntMap a -> IntMap b -> IntMap a
IntMap.difference StatusMap
smap IntMap ()
ok_tests)

    finish :: IntMap.IntMap () -> Int -> STM (IO Bool)
    finish :: IntMap () -> Int -> STM (IO Bool)
finish IntMap ()
ok_tests Int
_ = IntMap () -> STM (IO Bool)
next_iter IntMap ()
ok_tests

-- }}}

--------------------------------------------------
-- Console test reporter
--------------------------------------------------
-- {{{

-- | A simple console UI.
--
-- @since 0.4
consoleTestReporter :: Ingredient
consoleTestReporter :: Ingredient
consoleTestReporter = [OptionDescription]
-> (OptionSet
    -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool)))
-> Ingredient
TestReporter [OptionDescription]
consoleTestReporterOptions forall a b. (a -> b) -> a -> b
$ \OptionSet
opts TestTree
tree ->
  let
    TestPattern Maybe Expr
pattern = forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    tests :: [TestName]
tests = OptionSet -> TestTree -> [TestName]
testsNames OptionSet
opts TestTree
tree
    hook :: [TestName] -> Result -> IO Result
hook = (forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
.) forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TestName] -> Maybe Expr -> [TestName] -> Result -> Result
appendPatternIfTestFailed [TestName]
tests Maybe Expr
pattern
    TestReporter [OptionDescription]
_ OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool))
cb = ([TestName] -> Result -> IO Result) -> Ingredient
consoleTestReporterWithHook [TestName] -> Result -> IO Result
hook
  in OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool))
cb OptionSet
opts TestTree
tree

appendPatternIfTestFailed
  :: [TestName] -- ^ list of (pre-intercalated) test names
  -> Maybe Expr -- ^ current pattern, if any
  -> [TestName] -- ^ name of current test, represented as a list of group names
  -> Result     -- ^ vanilla result
  -> Result
appendPatternIfTestFailed :: [TestName] -> Maybe Expr -> [TestName] -> Result -> Result
appendPatternIfTestFailed [TestName
_] Maybe Expr
_ [TestName]
_ Result
res = Result
res -- if there is only one test, nothing to refine
appendPatternIfTestFailed [TestName]
_ Maybe Expr
_ [] Result
res  = Result
res -- should be impossible
appendPatternIfTestFailed [TestName]
tests Maybe Expr
currentPattern (TestName
name : [TestName]
names) Result
res = case Result -> Outcome
resultOutcome Result
res of
  Outcome
Success -> Result
res
  Failure{} -> Result
res { resultDescription :: TestName
resultDescription = Result -> TestName
resultDescription Result
res forall a. [a] -> [a] -> [a]
++ TestName
msg }
  where
    msg :: TestName
msg = TestName
"\nUse -p '" forall a. [a] -> [a] -> [a]
++ TestName -> TestName
escapeQuotes (Expr -> TestName
printAwkExpr Expr
pattern) forall a. [a] -> [a] -> [a]
++ TestName
"' to rerun this test only."

    escapeQuotes :: TestName -> TestName
escapeQuotes = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall a b. (a -> b) -> a -> b
$ \Char
c -> if Char
c forall a. Eq a => a -> a -> Bool
== Char
'\'' then TestName
"'\\''" else [Char
c]

    findPattern :: [TestName] -> TestName -> [TestName] -> Expr
findPattern [TestName
_] TestName
pat [TestName]
_ = TestName -> Expr
ERE TestName
pat
    findPattern [TestName]
_  TestName
pat [] = Expr -> Expr -> Expr
EQ (Expr -> Expr
Field (Int -> Expr
IntLit Int
0)) (TestName -> Expr
StringLit TestName
pat)
    findPattern [TestName]
ts TestName
pat (TestName
n : [TestName]
ns) = let pat' :: TestName
pat' = TestName
n forall a. [a] -> [a] -> [a]
++ Char
'.' forall a. a -> [a] -> [a]
: TestName
pat in
      [TestName] -> TestName -> [TestName] -> Expr
findPattern (forall a. (a -> Bool) -> [a] -> [a]
filter (TestName
pat' forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf`) [TestName]
ts) TestName
pat' [TestName]
ns

    individualPattern :: Expr
individualPattern = [TestName] -> TestName -> [TestName] -> Expr
findPattern (forall a. (a -> Bool) -> [a] -> [a]
filter (TestName
name forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf`) [TestName]
tests) TestName
name [TestName]
names

    pattern :: Expr
pattern = forall b a. b -> (a -> b) -> Maybe a -> b
maybe forall a. a -> a
id Expr -> Expr -> Expr
And Maybe Expr
currentPattern Expr
individualPattern

consoleTestReporterOptions :: [OptionDescription]
consoleTestReporterOptions :: [OptionDescription]
consoleTestReporterOptions =
  [ forall v. IsOption v => Proxy v -> OptionDescription
Option (forall {k} (t :: k). Proxy t
Proxy :: Proxy Quiet)
  , forall v. IsOption v => Proxy v -> OptionDescription
Option (forall {k} (t :: k). Proxy t
Proxy :: Proxy HideSuccesses)
  , forall v. IsOption v => Proxy v -> OptionDescription
Option (forall {k} (t :: k). Proxy t
Proxy :: Proxy MinDurationToReport)
  , forall v. IsOption v => Proxy v -> OptionDescription
Option (forall {k} (t :: k). Proxy t
Proxy :: Proxy UseColor)
  , forall v. IsOption v => Proxy v -> OptionDescription
Option (forall {k} (t :: k). Proxy t
Proxy :: Proxy AnsiTricks)
  ]

-- | A simple console UI with a hook to postprocess results,
-- depending on their names and external conditions
-- (e. g., its previous outcome, stored in a file).
-- Names are listed in reverse order:
-- from test's own name to a name of the outermost test group.
--
-- @since 1.4.2
consoleTestReporterWithHook :: ([TestName] -> Result -> IO Result) -> Ingredient
consoleTestReporterWithHook :: ([TestName] -> Result -> IO Result) -> Ingredient
consoleTestReporterWithHook [TestName] -> Result -> IO Result
hook = [OptionDescription]
-> (OptionSet
    -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool)))
-> Ingredient
TestReporter [OptionDescription]
consoleTestReporterOptions forall a b. (a -> b) -> a -> b
$
  \OptionSet
opts TestTree
tree -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ \StatusMap
smap -> do

  let
    whenColor :: UseColor
whenColor = forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    Quiet Bool
quiet = forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    HideSuccesses Bool
hideSuccesses = forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    NumThreads Int
numThreads = forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    AnsiTricks Bool
ansiTricks = forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts

  if Bool
quiet
    then do
      Bool
b <- Int -> StatusMap -> IO Bool
statusMapResult Int
numThreads StatusMap
smap
      forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ \Time
_time -> forall (m :: * -> *) a. Monad m => a -> m a
return Bool
b
    else

      do
      Bool
isTerm <- Handle -> IO Bool
hSupportsANSI Handle
stdout
      Bool
isTermColor <- Handle -> IO Bool
hSupportsANSIColor Handle
stdout

      (\IO (Time -> IO Bool)
k -> if Bool
isTerm
        then (do IO ()
hideCursor; IO (Time -> IO Bool)
k) forall a b. IO a -> IO b -> IO a
`finally` IO ()
showCursor
        else IO (Time -> IO Bool)
k) forall a b. (a -> b) -> a -> b
$ do

          Handle -> BufferMode -> IO ()
hSetBuffering Handle
stdout BufferMode
LineBuffering

          let
            ?colors = UseColor -> Bool -> Bool
useColor UseColor
whenColor Bool
isTermColor

          let
            -- 'buildTestOutput' is a pure function and cannot query 'hSupportsANSI' itself.
            -- We also would rather not pass @isTerm@ as an extra argument,
            -- since it's a breaking change, thus resorting to tweaking @opts@.
            opts' :: OptionSet
opts' = forall v. IsOption v => (v -> v) -> OptionSet -> OptionSet
changeOption (\(AnsiTricks Bool
x) -> Bool -> AnsiTricks
AnsiTricks (Bool
x Bool -> Bool -> Bool
&& Bool
isTerm)) OptionSet
opts
            toutput :: TestOutput
toutput = ([TestName] -> Result -> IO Result) -> TestOutput -> TestOutput
applyHook [TestName] -> Result -> IO Result
hook forall a b. (a -> b) -> a -> b
$ (?colors::Bool) => OptionSet -> TestTree -> TestOutput
buildTestOutput OptionSet
opts' TestTree
tree

          case () of { ()
_
            | Bool
hideSuccesses Bool -> Bool -> Bool
&& Bool
isTerm Bool -> Bool -> Bool
&& Bool
ansiTricks ->
                (?colors::Bool) => TestOutput -> StatusMap -> IO ()
consoleOutputHidingSuccesses TestOutput
toutput StatusMap
smap
            | Bool
hideSuccesses ->
                (?colors::Bool) => TestOutput -> StatusMap -> IO ()
streamOutputHidingSuccesses TestOutput
toutput StatusMap
smap
            | Bool
otherwise -> (?colors::Bool) => TestOutput -> StatusMap -> IO ()
consoleOutput TestOutput
toutput StatusMap
smap
          }

          forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ \Time
time -> do
            Statistics
stats <- StatusMap -> IO Statistics
computeStatistics StatusMap
smap
            (?colors::Bool) => Statistics -> Time -> IO ()
printStatistics Statistics
stats Time
time
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Statistics -> Int
statFailures Statistics
stats forall a. Eq a => a -> a -> Bool
== Int
0

-- | Do not print test results (see README for details).
--
-- @since 0.8
newtype Quiet = Quiet Bool
  deriving (Quiet -> Quiet -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Quiet -> Quiet -> Bool
$c/= :: Quiet -> Quiet -> Bool
== :: Quiet -> Quiet -> Bool
$c== :: Quiet -> Quiet -> Bool
Eq, Eq Quiet
Quiet -> Quiet -> Bool
Quiet -> Quiet -> Ordering
Quiet -> Quiet -> Quiet
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: Quiet -> Quiet -> Quiet
$cmin :: Quiet -> Quiet -> Quiet
max :: Quiet -> Quiet -> Quiet
$cmax :: Quiet -> Quiet -> Quiet
>= :: Quiet -> Quiet -> Bool
$c>= :: Quiet -> Quiet -> Bool
> :: Quiet -> Quiet -> Bool
$c> :: Quiet -> Quiet -> Bool
<= :: Quiet -> Quiet -> Bool
$c<= :: Quiet -> Quiet -> Bool
< :: Quiet -> Quiet -> Bool
$c< :: Quiet -> Quiet -> Bool
compare :: Quiet -> Quiet -> Ordering
$ccompare :: Quiet -> Quiet -> Ordering
Ord, Typeable)
instance IsOption Quiet where
  defaultValue :: Quiet
defaultValue = Bool -> Quiet
Quiet Bool
False
  parseValue :: TestName -> Maybe Quiet
parseValue = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Bool -> Quiet
Quiet forall b c a. (b -> c) -> (a -> b) -> a -> c
. TestName -> Maybe Bool
safeReadBool
  optionName :: Tagged Quiet TestName
optionName = forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"quiet"
  optionHelp :: Tagged Quiet TestName
optionHelp = forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"Do not produce any output; indicate success only by the exit code"
  optionCLParser :: Parser Quiet
optionCLParser = forall v. IsOption v => Mod FlagFields v -> v -> Parser v
mkFlagCLParser (forall (f :: * -> *) a. HasName f => Char -> Mod f a
short Char
'q') (Bool -> Quiet
Quiet Bool
True)

-- | Report only failed tests.
--
-- At the moment, this option only works globally. As an argument
-- to 'Test.Tasty.localOption', it does nothing.
--
-- @since 0.8
newtype HideSuccesses = HideSuccesses Bool
  deriving (HideSuccesses -> HideSuccesses -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: HideSuccesses -> HideSuccesses -> Bool
$c/= :: HideSuccesses -> HideSuccesses -> Bool
== :: HideSuccesses -> HideSuccesses -> Bool
$c== :: HideSuccesses -> HideSuccesses -> Bool
Eq, Eq HideSuccesses
HideSuccesses -> HideSuccesses -> Bool
HideSuccesses -> HideSuccesses -> Ordering
HideSuccesses -> HideSuccesses -> HideSuccesses
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: HideSuccesses -> HideSuccesses -> HideSuccesses
$cmin :: HideSuccesses -> HideSuccesses -> HideSuccesses
max :: HideSuccesses -> HideSuccesses -> HideSuccesses
$cmax :: HideSuccesses -> HideSuccesses -> HideSuccesses
>= :: HideSuccesses -> HideSuccesses -> Bool
$c>= :: HideSuccesses -> HideSuccesses -> Bool
> :: HideSuccesses -> HideSuccesses -> Bool
$c> :: HideSuccesses -> HideSuccesses -> Bool
<= :: HideSuccesses -> HideSuccesses -> Bool
$c<= :: HideSuccesses -> HideSuccesses -> Bool
< :: HideSuccesses -> HideSuccesses -> Bool
$c< :: HideSuccesses -> HideSuccesses -> Bool
compare :: HideSuccesses -> HideSuccesses -> Ordering
$ccompare :: HideSuccesses -> HideSuccesses -> Ordering
Ord, Typeable)
instance IsOption HideSuccesses where
  defaultValue :: HideSuccesses
defaultValue = Bool -> HideSuccesses
HideSuccesses Bool
False
  parseValue :: TestName -> Maybe HideSuccesses
parseValue = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Bool -> HideSuccesses
HideSuccesses forall b c a. (b -> c) -> (a -> b) -> a -> c
. TestName -> Maybe Bool
safeReadBool
  optionName :: Tagged HideSuccesses TestName
optionName = forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"hide-successes"
  optionHelp :: Tagged HideSuccesses TestName
optionHelp = forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"Do not print tests that passed successfully"
  optionCLParser :: Parser HideSuccesses
optionCLParser = forall v. IsOption v => Mod FlagFields v -> v -> Parser v
mkFlagCLParser forall a. Monoid a => a
mempty (Bool -> HideSuccesses
HideSuccesses Bool
True)

-- | The minimum amount of time a test can take before tasty
-- prints timing information.
--
-- @since 1.5
newtype MinDurationToReport = MinDurationToReport { MinDurationToReport -> Integer
minDurationMicros :: Integer }
  deriving (MinDurationToReport -> MinDurationToReport -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: MinDurationToReport -> MinDurationToReport -> Bool
$c/= :: MinDurationToReport -> MinDurationToReport -> Bool
== :: MinDurationToReport -> MinDurationToReport -> Bool
$c== :: MinDurationToReport -> MinDurationToReport -> Bool
Eq, Eq MinDurationToReport
MinDurationToReport -> MinDurationToReport -> Bool
MinDurationToReport -> MinDurationToReport -> Ordering
MinDurationToReport -> MinDurationToReport -> MinDurationToReport
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: MinDurationToReport -> MinDurationToReport -> MinDurationToReport
$cmin :: MinDurationToReport -> MinDurationToReport -> MinDurationToReport
max :: MinDurationToReport -> MinDurationToReport -> MinDurationToReport
$cmax :: MinDurationToReport -> MinDurationToReport -> MinDurationToReport
>= :: MinDurationToReport -> MinDurationToReport -> Bool
$c>= :: MinDurationToReport -> MinDurationToReport -> Bool
> :: MinDurationToReport -> MinDurationToReport -> Bool
$c> :: MinDurationToReport -> MinDurationToReport -> Bool
<= :: MinDurationToReport -> MinDurationToReport -> Bool
$c<= :: MinDurationToReport -> MinDurationToReport -> Bool
< :: MinDurationToReport -> MinDurationToReport -> Bool
$c< :: MinDurationToReport -> MinDurationToReport -> Bool
compare :: MinDurationToReport -> MinDurationToReport -> Ordering
$ccompare :: MinDurationToReport -> MinDurationToReport -> Ordering
Ord, Typeable)
instance IsOption MinDurationToReport where
  defaultValue :: MinDurationToReport
defaultValue = Integer -> MinDurationToReport
MinDurationToReport Integer
10000
  parseValue :: TestName -> Maybe MinDurationToReport
parseValue = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Integer -> MinDurationToReport
MinDurationToReport forall b c a. (b -> c) -> (a -> b) -> a -> c
. TestName -> Maybe Integer
parseDuration
  optionName :: Tagged MinDurationToReport TestName
optionName = forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"min-duration-to-report"
  optionHelp :: Tagged MinDurationToReport TestName
optionHelp =
    forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TestName] -> TestName
unwords forall a b. (a -> b) -> a -> b
$
      [ TestName
"The minimum amount of time a test can take before tasty prints timing information"
      , TestName
"(suffixes: ms,s,m,h; default: s)"
      ]
  optionCLParser :: Parser MinDurationToReport
optionCLParser = forall v. IsOption v => Mod OptionFields v -> Parser v
mkOptionCLParser (forall (f :: * -> *) a. HasMetavar f => TestName -> Mod f a
metavar TestName
"DURATION")

-- | When to use color on the output
--
-- @since 0.11.3
data UseColor
  = Never
  | Always
  | Auto -- ^ Only if stdout is an ANSI color supporting terminal
  deriving (UseColor -> UseColor -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: UseColor -> UseColor -> Bool
$c/= :: UseColor -> UseColor -> Bool
== :: UseColor -> UseColor -> Bool
$c== :: UseColor -> UseColor -> Bool
Eq, Eq UseColor
UseColor -> UseColor -> Bool
UseColor -> UseColor -> Ordering
UseColor -> UseColor -> UseColor
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: UseColor -> UseColor -> UseColor
$cmin :: UseColor -> UseColor -> UseColor
max :: UseColor -> UseColor -> UseColor
$cmax :: UseColor -> UseColor -> UseColor
>= :: UseColor -> UseColor -> Bool
$c>= :: UseColor -> UseColor -> Bool
> :: UseColor -> UseColor -> Bool
$c> :: UseColor -> UseColor -> Bool
<= :: UseColor -> UseColor -> Bool
$c<= :: UseColor -> UseColor -> Bool
< :: UseColor -> UseColor -> Bool
$c< :: UseColor -> UseColor -> Bool
compare :: UseColor -> UseColor -> Ordering
$ccompare :: UseColor -> UseColor -> Ordering
Ord, Typeable)

-- | Control color output
instance IsOption UseColor where
  defaultValue :: UseColor
defaultValue = UseColor
Auto
  parseValue :: TestName -> Maybe UseColor
parseValue = TestName -> Maybe UseColor
parseUseColor
  optionName :: Tagged UseColor TestName
optionName = forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"color"
  optionHelp :: Tagged UseColor TestName
optionHelp = forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"When to use colored output"
  optionCLParser :: Parser UseColor
optionCLParser = forall v. IsOption v => Mod OptionFields v -> Parser v
mkOptionCLParser forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. HasMetavar f => TestName -> Mod f a
metavar TestName
"never|always|auto"
  showDefaultValue :: UseColor -> Maybe TestName
showDefaultValue = forall a. a -> Maybe a
Just forall b c a. (b -> c) -> (a -> b) -> a -> c
. UseColor -> TestName
displayUseColor

-- | By default, when the option @--hide-successes@ is given and the output
-- goes to an ANSI-capable terminal, we employ some ANSI terminal tricks to
-- display the name of the currently running test and then erase it if it
-- succeeds.
--
-- These tricks sometimes fail, however—in particular, when the test names
-- happen to be longer than the width of the terminal window. See
--
-- * <https://github.com/UnkindPartition/tasty/issues/152>
--
-- * <https://github.com/UnkindPartition/tasty/issues/250>
--
-- When that happens, this option can be used to disable the tricks. In
-- that case, the test name will be printed only once the test fails.
--
-- @since 1.3
newtype AnsiTricks = AnsiTricks { AnsiTricks -> Bool
getAnsiTricks :: Bool }
  deriving Typeable

instance IsOption AnsiTricks where
  defaultValue :: AnsiTricks
defaultValue = Bool -> AnsiTricks
AnsiTricks Bool
True
  parseValue :: TestName -> Maybe AnsiTricks
parseValue = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Bool -> AnsiTricks
AnsiTricks forall b c a. (b -> c) -> (a -> b) -> a -> c
. TestName -> Maybe Bool
safeReadBool
  optionName :: Tagged AnsiTricks TestName
optionName = forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"ansi-tricks"
  optionHelp :: Tagged AnsiTricks TestName
optionHelp = forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
    -- Multiline literals don't work because of -XCPP.
    TestName
"Enable various ANSI terminal tricks. " forall a. [a] -> [a] -> [a]
++
    TestName
"Can be set to 'true' or 'false'."
  showDefaultValue :: AnsiTricks -> Maybe TestName
showDefaultValue = forall a. a -> Maybe a
Just forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> TestName
displayBool forall b c a. (b -> c) -> (a -> b) -> a -> c
. AnsiTricks -> Bool
getAnsiTricks

displayBool :: Bool -> String
displayBool :: Bool -> TestName
displayBool Bool
b =
  case Bool
b of
    Bool
False -> TestName
"false"
    Bool
True  -> TestName
"true"

-- | @useColor when isTerm@ decides if colors should be used,
--   where @isTerm@ indicates whether @stdout@ is a terminal device.
--
--   @since 0.11.3
useColor :: UseColor -> Bool -> Bool
useColor :: UseColor -> Bool -> Bool
useColor UseColor
when_ Bool
isTerm =
  case UseColor
when_ of
    UseColor
Never  -> Bool
False
    UseColor
Always -> Bool
True
    UseColor
Auto   -> Bool
isTerm

parseUseColor :: String -> Maybe UseColor
parseUseColor :: TestName -> Maybe UseColor
parseUseColor TestName
s =
  case forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower TestName
s of
    TestName
"never"  -> forall (m :: * -> *) a. Monad m => a -> m a
return UseColor
Never
    TestName
"always" -> forall (m :: * -> *) a. Monad m => a -> m a
return UseColor
Always
    TestName
"auto"   -> forall (m :: * -> *) a. Monad m => a -> m a
return UseColor
Auto
    TestName
_        -> forall a. Maybe a
Nothing

displayUseColor :: UseColor -> String
displayUseColor :: UseColor -> TestName
displayUseColor UseColor
uc =
  case UseColor
uc of
    UseColor
Never  -> TestName
"never"
    UseColor
Always -> TestName
"always"
    UseColor
Auto   -> TestName
"auto"

-- }}}

--------------------------------------------------
-- Various utilities
--------------------------------------------------
-- {{{
getResultFromTVar :: TVar Status -> IO Result
getResultFromTVar :: TVar Status -> IO Result
getResultFromTVar TVar Status
var =
  forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
    Status
status <- forall a. TVar a -> STM a
readTVar TVar Status
var
    case Status
status of
      Done Result
r -> forall (m :: * -> *) a. Monad m => a -> m a
return Result
r
      Status
_ -> forall a. STM a
retry

-- }}}

--------------------------------------------------
-- Formatting
--------------------------------------------------
-- {{{

indentSize :: Int
indentSize :: Int
indentSize = Int
2

indent :: Int -> String
indent :: Int -> TestName
indent Int
n = forall a. Int -> a -> [a]
replicate (Int
indentSize forall a. Num a => a -> a -> a
* Int
n) Char
' '

-- handle multi-line result descriptions properly
formatDesc
  :: Int -- indent
  -> String
  -> String
formatDesc :: Int -> TestName -> TestName
formatDesc Int
n TestName
desc =
  let
    -- remove all trailing linebreaks
    chomped :: TestName
chomped = forall a. [a] -> [a]
reverse forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
dropWhile (forall a. Eq a => a -> a -> Bool
== Char
'\n') forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse forall a b. (a -> b) -> a -> b
$ TestName
desc

    multiline :: Bool
multiline = Char
'\n' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` TestName
chomped

    -- we add a leading linebreak to the description, to start it on a new
    -- line and add an indentation
    paddedDesc :: TestName
paddedDesc = forall a b c. (a -> b -> c) -> b -> a -> c
flip forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap TestName
chomped forall a b. (a -> b) -> a -> b
$ \Char
c ->
      if Char
c forall a. Eq a => a -> a -> Bool
== Char
'\n'
        then Char
c forall a. a -> [a] -> [a]
: Int -> TestName
indent Int
n
        else [Char
c]
  in
    if Bool
multiline
      then TestName
paddedDesc
      else TestName
chomped

data Maximum a
  = Maximum a
  | MinusInfinity

instance Ord a => Sem.Semigroup (Maximum a) where
  Maximum a
a <> :: Maximum a -> Maximum a -> Maximum a
<> Maximum a
b = forall a. a -> Maximum a
Maximum (a
a forall a. Ord a => a -> a -> a
`max` a
b)
  Maximum a
MinusInfinity <> Maximum a
a = Maximum a
a
  Maximum a
a <> Maximum a
MinusInfinity = Maximum a
a
instance Ord a => Monoid (Maximum a) where
  mempty :: Maximum a
mempty = forall a. Maximum a
MinusInfinity
#if !MIN_VERSION_base(4,11,0)
  mappend = (Sem.<>)
#endif

-- | Compute the amount of space needed to align \"OK\"s and \"FAIL\"s
computeAlignment :: OptionSet -> TestTree -> Int
computeAlignment :: OptionSet -> TestTree -> Int
computeAlignment OptionSet
opts =
  forall {t} {a}. (Num t, Num a) => (t -> Maximum a) -> a
fromMonoid forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  forall b. Monoid b => TreeFold b -> OptionSet -> TestTree -> b
foldTestTree
    forall b. Monoid b => TreeFold b
trivialFold
      { foldSingle :: forall t.
IsTest t =>
OptionSet -> TestName -> t -> Int -> Maximum Int
foldSingle = \OptionSet
_ TestName
name t
_ Int
level -> forall a. a -> Maximum a
Maximum (TestName -> Int
stringWidth TestName
name forall a. Num a => a -> a -> a
+ Int
level)
      , foldGroup :: OptionSet -> TestName -> [Int -> Maximum Int] -> Int -> Maximum Int
foldGroup = \OptionSet
_opts TestName
_ [Int -> Maximum Int]
m -> forall a. Monoid a => [a] -> a
mconcat [Int -> Maximum Int]
m forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a. Num a => a -> a -> a
+ Int
indentSize)
      }
    OptionSet
opts
  where
    fromMonoid :: (t -> Maximum a) -> a
fromMonoid t -> Maximum a
m =
      case t -> Maximum a
m t
0 of
        Maximum a
MinusInfinity -> a
0
        Maximum a
x -> a
x

-- | Compute the length/width of the string as it would appear in a monospace
--   terminal. This takes into account that even in a “mono”space font, not
--   all characters actually have the same width, in particular, most CJK
--   characters have twice the same as Western characters.
--
--   (This only works properly on Unix at the moment; on Windows, the function
--   treats every character as width-1 like 'Data.List.length' does.)
stringWidth :: String -> Int
#ifdef USE_WCWIDTH
stringWidth :: TestName -> Int
stringWidth = forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
Prelude.sum forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map forall {a}. Num a => Char -> a
charWidth
 where charWidth :: Char -> a
charWidth Char
c = case CWchar -> CInt
wcwidth (forall a b. (Integral a, Num b) => a -> b
fromIntegral (Char -> Int
ord Char
c)) of
        -1 -> a
1  -- many chars have "undefined" width; default to 1 for these.
        CInt
w  -> forall a b. (Integral a, Num b) => a -> b
fromIntegral CInt
w
foreign import capi safe "wchar.h wcwidth" wcwidth :: CWchar -> CInt
#else
stringWidth = length
#endif

-- (Potentially) colorful output
ok, fail, skipped, infoOk, infoFail :: (?colors :: Bool) => String -> IO ()
fail :: (?colors::Bool) => TestName -> IO ()
fail     = (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
failFormat
ok :: (?colors::Bool) => TestName -> IO ()
ok       = (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
okFormat
skipped :: (?colors::Bool) => TestName -> IO ()
skipped  = (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
skippedFormat
-- Just default foreground color for 'infoOk'; do not apply 'infoOkFormat',
-- because terminal's background could be white itself. See #298.
infoOk :: (?colors::Bool) => TestName -> IO ()
infoOk   = TestName -> IO ()
putStr
infoFail :: (?colors::Bool) => TestName -> IO ()
infoFail = (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
infoFailFormat

output
  :: (?colors :: Bool)
  => ConsoleFormat
  -> String
  -> IO ()
output :: (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
format = (?colors::Bool) => ConsoleFormatPrinter
withConsoleFormat ConsoleFormat
format forall b c a. (b -> c) -> (a -> b) -> a -> c
. TestName -> IO ()
putStr

-- | Run action with console configured for a specific output format
--
-- This function does not apply any output formats if colors are disabled at command
-- line or console detection.
--
-- Can be used by providers that wish to provider specific result details printing,
-- while re-using the tasty formats and coloring logic.
--
-- @since 1.3.1
withConsoleFormat :: (?colors :: Bool) => ConsoleFormatPrinter
withConsoleFormat :: (?colors::Bool) => ConsoleFormatPrinter
withConsoleFormat ConsoleFormat
format IO ()
action
  | ?colors::Bool
?colors =
    (do
      [SGR] -> IO ()
setSGR
        [ ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground (ConsoleFormat -> ColorIntensity
colorIntensity ConsoleFormat
format) (ConsoleFormat -> Color
color ConsoleFormat
format)
        , ConsoleIntensity -> SGR
SetConsoleIntensity (ConsoleFormat -> ConsoleIntensity
consoleIntensity ConsoleFormat
format)
        ]
      IO ()
action
    ) forall a b. IO a -> IO b -> IO a
`finally` [SGR] -> IO ()
setSGR []
  | Bool
otherwise = IO ()
action

-- }}}