-- vim:fdm=marker
{-# LANGUAGE BangPatterns, ImplicitParams, MultiParamTypeClasses, DeriveDataTypeable, FlexibleContexts #-}
-- | Console reporter ingredient
module Test.Tasty.Ingredients.ConsoleReporter
  ( consoleTestReporter
  , Quiet(..)
  , HideSuccesses(..)
  , 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)
import Control.Monad.State hiding (fail)
import Control.Monad.Reader hiding (fail,reader)
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.Options
import Test.Tasty.Options.Core
import Test.Tasty.Runners.Reducers
import Test.Tasty.Runners.Utils
import Text.Printf
import qualified Data.IntMap as IntMap
import Data.Char
#ifdef VERSION_wcwidth
import Data.Char.WCWidth (wcwidth)
#endif
import Data.Maybe
import Data.Monoid (Any(..))
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.Semigroup (Semigroup)
import qualified Data.Semigroup (Semigroup((<>)))
import Data.Monoid
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
  = PrintTest
      {- test name         -} String
      {- print test name   -} (IO ())
      {- print test result -} (Result -> IO ())
      -- ^ Name of a test, an action that prints the test name, and an action
      -- that renders the result of the action.
  | PrintHeading String (IO ()) TestOutput
      -- ^ Name of a test group, an action that prints the heading of a test
      -- group and the 'TestOutput' for that test group.
  | Skip -- ^ Inactive test (e.g. not matching the current pattern)
  | Seq TestOutput TestOutput -- ^ Two sets of 'TestOuput' on the same level

-- The monoid laws should hold observationally w.r.t. the semantics defined
-- in this module
instance Monoid TestOutput where
  mempty :: TestOutput
mempty = TestOutput
Skip
  mappend :: TestOutput -> TestOutput -> TestOutput
mappend = TestOutput -> TestOutput -> TestOutput
Seq
instance Semigroup TestOutput where
  <> :: TestOutput -> TestOutput -> TestOutput
(<>) = TestOutput -> TestOutput -> TestOutput
forall a. Monoid a => a -> a -> a
mappend

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 :: 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

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

      let
        printTestName :: IO ()
printTestName = do
          TestName -> TestName -> TestName -> TestName -> IO ()
forall r. PrintfType r => TestName -> r
printf TestName
"%s%s: %s" (Int -> TestName
indent Int
level) TestName
name
            (Int -> Char -> TestName
forall a. Int -> a -> [a]
replicate (Int
alignment Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
indentSize Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
level Int -> Int -> Int
forall a. Num a => a -> a -> a
- TestName -> Int
stringWidth TestName
name) Char
' ')
          Handle -> IO ()
hFlush Handle
stdout

        printTestResult :: Result -> IO ()
printTestResult Result
result = do
          TestName
rDesc <- TestName -> IO TestName
formatMessage (TestName -> IO TestName) -> TestName -> IO TestName
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 ()
TestName -> IO ()
ok
                Failure FailureReason
TestDepFailed -> (?colors::Bool) => TestName -> IO ()
TestName -> IO ()
skipped
                Outcome
_ -> (?colors::Bool) => TestName -> IO ()
TestName -> IO ()
fail
            time :: Time
time = Result -> Time
resultTime Result
result
          TestName -> IO ()
printFn (Result -> TestName
resultShortDescription Result
result)
          -- print time only if it's significant
          Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Time
time Time -> Time -> Bool
forall a. Ord a => a -> a -> Bool
>= Time
0.01) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
            TestName -> IO ()
printFn (TestName -> Time -> TestName
forall r. PrintfType r => TestName -> r
printf TestName
" (%.2fs)" Time
time)
          TestName -> IO ()
printFn TestName
"\n"

          Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ TestName -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null TestName
rDesc) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
            (if Result -> Bool
resultSuccessful Result
result then (?colors::Bool) => TestName -> IO ()
TestName -> IO ()
infoOk else (?colors::Bool) => TestName -> IO ()
TestName -> IO ()
infoFail) (TestName -> IO ()) -> TestName -> IO ()
forall a b. (a -> b) -> a -> b
$
              TestName -> TestName -> TestName -> TestName
forall r. PrintfType r => TestName -> r
printf TestName
"%s%s\n" (Int -> TestName
indent (Int -> TestName) -> Int -> TestName
forall a b. (a -> b) -> a -> b
$ Int
level Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (Int -> TestName -> TestName
formatDesc (Int
levelInt -> Int -> Int
forall 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
ConsoleFormatPrinter
withConsoleFormat

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

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

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

-- | 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 :: (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 =
  (State Int b -> Int -> b) -> Int -> State Int b -> b
forall a b c. (a -> b -> c) -> b -> a -> c
flip State Int b -> Int -> b
forall s a. State s a -> s -> a
evalState Int
0 (State Int b -> b) -> State Int b -> b
forall a b. (a -> b) -> a -> b
$ Ap (StateT Int Identity) b -> State Int b
forall (f :: * -> *) a. Ap f a -> f a
getApp (Ap (StateT Int Identity) b -> State Int b)
-> Ap (StateT Int Identity) b -> State Int b
forall a b. (a -> b) -> a -> b
$ TestOutput -> Ap (StateT Int Identity) b
forall (f :: * -> *). MonadState Int f => TestOutput -> Ap f b
go TestOutput
outputTree where
  go :: TestOutput -> Ap f b
go (PrintTest TestName
name IO ()
printName Result -> IO ()
printResult) = f b -> Ap f b
forall (f :: * -> *) a. f a -> Ap f a
Ap (f b -> Ap f b) -> f b -> Ap f b
forall a b. (a -> b) -> a -> b
$ do
    Int
ix <- f Int
forall s (m :: * -> *). MonadState s m => m s
get
    Int -> f ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put (Int -> f ()) -> Int -> f ()
forall a b. (a -> b) -> a -> b
$! Int
ix Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
    let
      statusVar :: TVar Status
statusVar =
        TVar Status -> Maybe (TVar Status) -> TVar Status
forall a. a -> Maybe a -> a
fromMaybe (TestName -> TVar Status
forall a. HasCallStack => TestName -> a
error TestName
"internal error: index out of bounds") (Maybe (TVar Status) -> TVar Status)
-> Maybe (TVar Status) -> TVar Status
forall a b. (a -> b) -> a -> b
$
        Int -> StatusMap -> Maybe (TVar Status)
forall a. Int -> IntMap a -> Maybe a
IntMap.lookup Int
ix StatusMap
smap
      readStatusVar :: IO Result
readStatusVar = TVar Status -> IO Result
getResultFromTVar TVar Status
statusVar
    b -> f b
forall (m :: * -> *) a. Monad m => a -> m a
return (b -> f b) -> b -> f b
forall a b. (a -> b) -> a -> b
$ TestName -> IO () -> IO Result -> (Result -> IO ()) -> b
foldTest TestName
name IO ()
printName IO Result
readStatusVar Result -> IO ()
printResult
  go (PrintHeading TestName
name IO ()
printName TestOutput
printBody) = f b -> Ap f b
forall (f :: * -> *) a. f a -> Ap f a
Ap (f b -> Ap f b) -> f b -> Ap f b
forall a b. (a -> b) -> a -> b
$
    TestName -> IO () -> b -> b
foldHeading TestName
name IO ()
printName (b -> b) -> f b -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Ap f b -> f b
forall (f :: * -> *) a. Ap f a -> f a
getApp (TestOutput -> Ap f b
go TestOutput
printBody)
  go (Seq TestOutput
a TestOutput
b) = Ap f b -> Ap f b -> Ap f b
forall a. Monoid a => a -> a -> a
mappend (TestOutput -> Ap f b
go TestOutput
a) (TestOutput -> Ap f b
go TestOutput
b)
  go TestOutput
Skip = Ap f b
forall a. Monoid a => a
mempty

-- }}}

--------------------------------------------------
-- TestOutput modes
--------------------------------------------------
-- {{{
consoleOutput :: (?colors :: Bool) => TestOutput -> StatusMap -> IO ()
consoleOutput :: TestOutput -> StatusMap -> IO ()
consoleOutput TestOutput
toutput StatusMap
smap =
  Traversal IO -> IO ()
forall (f :: * -> *). Traversal f -> f ()
getTraversal (Traversal IO -> IO ())
-> ((Traversal IO, Any) -> Traversal IO)
-> (Traversal IO, Any)
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Traversal IO, Any) -> Traversal IO
forall a b. (a, b) -> a
fst ((Traversal IO, Any) -> IO ()) -> (Traversal IO, Any) -> IO ()
forall a b. (a -> b) -> a -> b
$ (TestName
 -> IO () -> IO Result -> (Result -> IO ()) -> (Traversal IO, Any))
-> (TestName
    -> IO () -> (Traversal IO, Any) -> (Traversal IO, Any))
-> TestOutput
-> StatusMap
-> (Traversal IO, Any)
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 ()) -> (Traversal IO, Any)
forall p t.
p -> IO () -> IO t -> (t -> IO ()) -> (Traversal IO, Any)
foldTest TestName -> IO () -> (Traversal IO, Any) -> (Traversal IO, Any)
forall p. p -> IO () -> (Traversal IO, Any) -> (Traversal IO, 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 =
      ( IO () -> Traversal IO
forall (f :: * -> *). f () -> Traversal f
Traversal (IO () -> Traversal IO) -> IO () -> Traversal IO
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 -> IO () -> (Traversal IO, Any) -> (Traversal IO, Any)
foldHeading p
_name IO ()
printHeading (Traversal IO
printBody, Any Bool
nonempty) =
      ( IO () -> Traversal IO
forall (f :: * -> *). f () -> Traversal f
Traversal (IO () -> Traversal IO) -> IO () -> Traversal IO
forall a b. (a -> b) -> a -> b
$ do
          Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
nonempty (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do IO ()
printHeading :: IO (); Traversal IO -> IO ()
forall (f :: * -> *). Traversal f -> f ()
getTraversal Traversal IO
printBody
      , Bool -> Any
Any Bool
nonempty
      )

consoleOutputHidingSuccesses :: (?colors :: Bool) => TestOutput -> StatusMap -> IO ()
consoleOutputHidingSuccesses :: TestOutput -> StatusMap -> IO ()
consoleOutputHidingSuccesses TestOutput
toutput StatusMap
smap =
  IO Any -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO Any -> IO ()) -> (Ap IO Any -> IO Any) -> Ap IO Any -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Ap IO Any -> IO Any
forall (f :: * -> *) a. Ap f a -> f a
getApp (Ap IO Any -> IO ()) -> Ap IO Any -> IO ()
forall a b. (a -> b) -> a -> b
$ (TestName -> IO () -> IO Result -> (Result -> IO ()) -> Ap IO Any)
-> (TestName -> IO () -> Ap IO Any -> Ap IO Any)
-> TestOutput
-> StatusMap
-> Ap IO Any
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 ()) -> Ap IO Any
forall p. p -> IO () -> IO Result -> (Result -> IO ()) -> Ap IO Any
foldTest TestName -> IO () -> Ap IO Any -> Ap IO Any
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 =
      IO Any -> Ap IO Any
forall (f :: * -> *) a. f a -> Ap f a
Ap (IO Any -> Ap IO Any) -> IO Any -> Ap IO Any
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; Any -> IO Any
forall (m :: * -> *) a. Monad m => a -> m a
return (Any -> IO Any) -> Any -> IO Any
forall a b. (a -> b) -> a -> b
$ Bool -> Any
Any Bool
False
            else do Result -> IO ()
printResult Result
r :: IO (); Any -> IO Any
forall (m :: * -> *) a. Monad m => a -> m a
return (Any -> IO Any) -> Any -> IO Any
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 =
      IO Any -> Ap IO Any
forall (f :: * -> *) a. f a -> Ap f a
Ap (IO Any -> Ap IO Any) -> IO Any -> Ap IO Any
forall a b. (a -> b) -> a -> b
$ do
        IO ()
printHeading :: IO ()
        Any Bool
failed <- Ap IO Any -> IO Any
forall (f :: * -> *) a. Ap f a -> f a
getApp Ap IO Any
printBody
        Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
failed IO ()
clearAboveLine
        Any -> IO Any
forall (m :: * -> *) a. Monad m => a -> m a
return (Any -> IO Any) -> Any -> IO Any
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 :: TestOutput -> StatusMap -> IO ()
streamOutputHidingSuccesses TestOutput
toutput StatusMap
smap =
  IO Any -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO Any -> IO ())
-> (Ap (StateT [IO ()] IO) Any -> IO Any)
-> Ap (StateT [IO ()] IO) Any
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (StateT [IO ()] IO Any -> [IO ()] -> IO Any)
-> [IO ()] -> StateT [IO ()] IO Any -> IO Any
forall a b c. (a -> b -> c) -> b -> a -> c
flip StateT [IO ()] IO Any -> [IO ()] -> IO Any
forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m a
evalStateT [] (StateT [IO ()] IO Any -> IO Any)
-> (Ap (StateT [IO ()] IO) Any -> StateT [IO ()] IO Any)
-> Ap (StateT [IO ()] IO) Any
-> IO Any
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Ap (StateT [IO ()] IO) Any -> StateT [IO ()] IO Any
forall (f :: * -> *) a. Ap f a -> f a
getApp (Ap (StateT [IO ()] IO) Any -> IO ())
-> Ap (StateT [IO ()] IO) Any -> IO ()
forall a b. (a -> b) -> a -> b
$
    (TestName
 -> IO ()
 -> IO Result
 -> (Result -> IO ())
 -> Ap (StateT [IO ()] IO) Any)
-> (TestName
    -> IO ()
    -> Ap (StateT [IO ()] IO) Any
    -> Ap (StateT [IO ()] IO) Any)
-> TestOutput
-> StatusMap
-> Ap (StateT [IO ()] IO) Any
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 ())
-> Ap (StateT [IO ()] IO) Any
forall a (f :: * -> *) p.
(MonadState [IO a] f, MonadIO f) =>
p -> IO () -> IO Result -> (Result -> IO ()) -> Ap f Any
foldTest TestName
-> IO ()
-> Ap (StateT [IO ()] IO) Any
-> Ap (StateT [IO ()] IO) Any
forall a (f :: * -> *) p.
MonadState [a] f =>
p -> a -> Ap f Any -> Ap f Any
foldHeading TestOutput
toutput StatusMap
smap
  where
    foldTest :: p -> IO () -> IO Result -> (Result -> IO ()) -> Ap f Any
foldTest p
_name IO ()
printName IO Result
getResult Result -> IO ()
printResult =
      f Any -> Ap f Any
forall (f :: * -> *) a. f a -> Ap f a
Ap (f Any -> Ap f Any) -> f Any -> Ap f Any
forall a b. (a -> b) -> a -> b
$ do
          Result
r <- IO Result -> f Result
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Result -> f Result) -> IO Result -> f Result
forall a b. (a -> b) -> a -> b
$ IO Result
getResult
          if Result -> Bool
resultSuccessful Result
r
            then Any -> f Any
forall (m :: * -> *) a. Monad m => a -> m a
return (Any -> f Any) -> Any -> f Any
forall a b. (a -> b) -> a -> b
$ Bool -> Any
Any Bool
False
            else do
              [IO a]
stack <- f [IO a]
forall s (m :: * -> *). MonadState s m => m s
get
              [IO a] -> f ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put []

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

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

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

instance Monoid Statistics where
  Statistics Int
t1 Int
f1 mappend :: Statistics -> Statistics -> Statistics
`mappend` Statistics Int
t2 Int
f2 = Int -> Int -> Statistics
Statistics (Int
t1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
t2) (Int
f1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
f2)
  mempty :: Statistics
mempty = Int -> Int -> Statistics
Statistics Int
0 Int
0
instance Semigroup Statistics where
  <> :: Statistics -> Statistics -> Statistics
(<>) = Statistics -> Statistics -> Statistics
forall a. Monoid a => a -> a -> a
mappend

-- | @computeStatistics@ computes a summary 'Statistics' for
-- a given state of the 'StatusMap'.
-- Useful in combination with @printStatistics@
computeStatistics :: StatusMap -> IO Statistics
computeStatistics :: StatusMap -> IO Statistics
computeStatistics = Ap IO Statistics -> IO Statistics
forall (f :: * -> *) a. Ap f a -> f a
getApp (Ap IO Statistics -> IO Statistics)
-> (StatusMap -> Ap IO Statistics) -> StatusMap -> IO Statistics
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (TVar Status -> Ap IO Statistics) -> StatusMap -> Ap IO Statistics
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap (\TVar Status
var -> IO Statistics -> Ap IO Statistics
forall (f :: * -> *) a. f a -> Ap f a
Ap (IO Statistics -> Ap IO Statistics)
-> IO Statistics -> Ap IO Statistics
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))
    (Result -> Statistics) -> IO Result -> IO Statistics
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 :: Statistics -> IO ()
reportStatistics Statistics
st = case Statistics -> Int
statFailures Statistics
st of
    Int
0 -> (?colors::Bool) => TestName -> IO ()
TestName -> IO ()
ok (TestName -> IO ()) -> TestName -> IO ()
forall a b. (a -> b) -> a -> b
$ TestName -> Int -> TestName
forall r. PrintfType r => TestName -> r
printf TestName
"All %d tests passed" (Statistics -> Int
statTotal Statistics
st)
    Int
fs -> (?colors::Bool) => TestName -> IO ()
TestName -> IO ()
fail (TestName -> IO ()) -> TestName -> IO ()
forall a b. (a -> b) -> a -> b
$ TestName -> Int -> Int -> TestName
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 :: Statistics -> Time -> IO ()
printStatistics Statistics
st Time
time = do
  TestName -> IO ()
forall r. PrintfType r => TestName -> r
printf TestName
"\n"
  (?colors::Bool) => Statistics -> IO ()
Statistics -> IO ()
reportStatistics Statistics
st
  case Statistics -> Int
statFailures Statistics
st of
    Int
0 -> (?colors::Bool) => TestName -> IO ()
TestName -> IO ()
ok (TestName -> IO ()) -> TestName -> IO ()
forall a b. (a -> b) -> a -> b
$ TestName -> Time -> TestName
forall r. PrintfType r => TestName -> r
printf TestName
" (%.2fs)\n" Time
time
    Int
_ -> (?colors::Bool) => TestName -> IO ()
TestName -> IO ()
fail (TestName -> IO ()) -> TestName -> IO ()
forall a b. (a -> b) -> a -> b
$ TestName -> Time -> TestName
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 :: Statistics -> IO ()
printStatisticsNoTime Statistics
st = (?colors::Bool) => Statistics -> IO ()
Statistics -> IO ()
reportStatistics Statistics
st IO () -> IO () -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> TestName -> IO ()
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
  | StatusMap -> Bool
forall a. IntMap a -> Bool
IntMap.null StatusMap
smap = Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
  | Bool
otherwise =
      IO (IO Bool) -> IO Bool
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join (IO (IO Bool) -> IO Bool)
-> (STM (IO Bool) -> IO (IO Bool)) -> STM (IO Bool) -> IO Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. STM (IO Bool) -> IO (IO Bool)
forall a. STM a -> IO a
atomically (STM (IO Bool) -> IO Bool) -> STM (IO Bool) -> IO Bool
forall a b. (a -> b) -> a -> b
$
        (Int
 -> TVar Status
 -> (IntMap () -> Int -> STM (IO Bool))
 -> IntMap ()
 -> Int
 -> STM (IO Bool))
-> (IntMap () -> Int -> STM (IO Bool))
-> StatusMap
-> IntMap ()
-> Int
-> STM (IO Bool)
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 IntMap ()
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 Int -> Int -> Bool
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 <- TVar Status -> STM 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 (Int -> () -> IntMap () -> IntMap ()
forall a. Int -> a -> IntMap a -> IntMap a
IntMap.insert Int
key () IntMap ()
ok_tests) Int
lookahead
                else IO Bool -> STM (IO Bool)
forall (m :: * -> *) a. Monad m => a -> m a
return (IO Bool -> STM (IO Bool)) -> IO Bool -> STM (IO Bool)
forall a b. (a -> b) -> a -> b
$ Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            Status
_ -> IntMap () -> Int -> STM (IO Bool)
k IntMap ()
ok_tests (Int
lookaheadInt -> Int -> Int
forall 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 IntMap () -> Bool
forall a. IntMap a -> Bool
IntMap.null IntMap ()
ok_tests
        then STM (IO Bool)
forall a. STM a
retry
        else IO Bool -> STM (IO Bool)
forall (m :: * -> *) a. Monad m => a -> m a
return (IO Bool -> STM (IO Bool)) -> IO Bool -> STM (IO Bool)
forall a b. (a -> b) -> a -> b
$ Int -> StatusMap -> IO Bool
statusMapResult Int
lookahead0 (StatusMap -> IntMap () -> StatusMap
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
consoleTestReporter :: Ingredient
consoleTestReporter :: Ingredient
consoleTestReporter =
  [OptionDescription]
-> (OptionSet
    -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool)))
-> Ingredient
TestReporter
    [ Proxy Quiet -> OptionDescription
forall v. IsOption v => Proxy v -> OptionDescription
Option (Proxy Quiet
forall k (t :: k). Proxy t
Proxy :: Proxy Quiet)
    , Proxy HideSuccesses -> OptionDescription
forall v. IsOption v => Proxy v -> OptionDescription
Option (Proxy HideSuccesses
forall k (t :: k). Proxy t
Proxy :: Proxy HideSuccesses)
    , Proxy UseColor -> OptionDescription
forall v. IsOption v => Proxy v -> OptionDescription
Option (Proxy UseColor
forall k (t :: k). Proxy t
Proxy :: Proxy UseColor)
    , Proxy AnsiTricks -> OptionDescription
forall v. IsOption v => Proxy v -> OptionDescription
Option (Proxy AnsiTricks
forall k (t :: k). Proxy t
Proxy :: Proxy AnsiTricks)
    ] ((OptionSet
  -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool)))
 -> Ingredient)
-> (OptionSet
    -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool)))
-> Ingredient
forall a b. (a -> b) -> a -> b
$
  \OptionSet
opts TestTree
tree -> (StatusMap -> IO (Time -> IO Bool))
-> Maybe (StatusMap -> IO (Time -> IO Bool))
forall a. a -> Maybe a
Just ((StatusMap -> IO (Time -> IO Bool))
 -> Maybe (StatusMap -> IO (Time -> IO Bool)))
-> (StatusMap -> IO (Time -> IO Bool))
-> Maybe (StatusMap -> IO (Time -> IO Bool))
forall a b. (a -> b) -> a -> b
$ \StatusMap
smap -> do

  let
    whenColor :: UseColor
whenColor = OptionSet -> UseColor
forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    Quiet Bool
quiet = OptionSet -> Quiet
forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    HideSuccesses Bool
hideSuccesses = OptionSet -> HideSuccesses
forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    NumThreads Int
numThreads = OptionSet -> NumThreads
forall v. IsOption v => OptionSet -> v
lookupOption OptionSet
opts
    AnsiTricks Bool
ansiTricks = OptionSet -> 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
      (Time -> IO Bool) -> IO (Time -> IO Bool)
forall (m :: * -> *) a. Monad m => a -> m a
return ((Time -> IO Bool) -> IO (Time -> IO Bool))
-> (Time -> IO Bool) -> IO (Time -> IO Bool)
forall a b. (a -> b) -> a -> b
$ \Time
_time -> Bool -> IO Bool
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) IO (Time -> IO Bool) -> IO () -> IO (Time -> IO Bool)
forall a b. IO a -> IO b -> IO a
`finally` IO ()
showCursor
        else IO (Time -> IO Bool)
k) (IO (Time -> IO Bool) -> IO (Time -> IO Bool))
-> IO (Time -> IO Bool) -> IO (Time -> IO Bool)
forall a b. (a -> b) -> a -> b
$ do

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

          let
            ?colors = useColor whenColor isTermColor

          let
            toutput :: TestOutput
toutput = (?colors::Bool) => OptionSet -> TestTree -> TestOutput
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 ()
TestOutput -> StatusMap -> IO ()
consoleOutputHidingSuccesses TestOutput
toutput StatusMap
smap
            | Bool
hideSuccesses ->
                (?colors::Bool) => TestOutput -> StatusMap -> IO ()
TestOutput -> StatusMap -> IO ()
streamOutputHidingSuccesses TestOutput
toutput StatusMap
smap
            | Bool
otherwise -> (?colors::Bool) => TestOutput -> StatusMap -> IO ()
TestOutput -> StatusMap -> IO ()
consoleOutput TestOutput
toutput StatusMap
smap
          }

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

-- | Do not print test results (see README for details)
newtype Quiet = Quiet Bool
  deriving (Quiet -> Quiet -> Bool
(Quiet -> Quiet -> Bool) -> (Quiet -> Quiet -> Bool) -> Eq Quiet
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
Eq Quiet
-> (Quiet -> Quiet -> Ordering)
-> (Quiet -> Quiet -> Bool)
-> (Quiet -> Quiet -> Bool)
-> (Quiet -> Quiet -> Bool)
-> (Quiet -> Quiet -> Bool)
-> (Quiet -> Quiet -> Quiet)
-> (Quiet -> Quiet -> Quiet)
-> Ord 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
$cp1Ord :: Eq Quiet
Ord, Typeable)
instance IsOption Quiet where
  defaultValue :: Quiet
defaultValue = Bool -> Quiet
Quiet Bool
False
  parseValue :: TestName -> Maybe Quiet
parseValue = (Bool -> Quiet) -> Maybe Bool -> Maybe Quiet
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Bool -> Quiet
Quiet (Maybe Bool -> Maybe Quiet)
-> (TestName -> Maybe Bool) -> TestName -> Maybe Quiet
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TestName -> Maybe Bool
safeReadBool
  optionName :: Tagged Quiet TestName
optionName = TestName -> Tagged Quiet TestName
forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"quiet"
  optionHelp :: Tagged Quiet TestName
optionHelp = TestName -> Tagged Quiet TestName
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 = Mod FlagFields Quiet -> Quiet -> Parser Quiet
forall v. IsOption v => Mod FlagFields v -> v -> Parser v
mkFlagCLParser (Char -> Mod FlagFields Quiet
forall (f :: * -> *) a. HasName f => Char -> Mod f a
short Char
'q') (Bool -> Quiet
Quiet Bool
True)

-- | Report only failed tests
newtype HideSuccesses = HideSuccesses Bool
  deriving (HideSuccesses -> HideSuccesses -> Bool
(HideSuccesses -> HideSuccesses -> Bool)
-> (HideSuccesses -> HideSuccesses -> Bool) -> Eq HideSuccesses
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
Eq HideSuccesses
-> (HideSuccesses -> HideSuccesses -> Ordering)
-> (HideSuccesses -> HideSuccesses -> Bool)
-> (HideSuccesses -> HideSuccesses -> Bool)
-> (HideSuccesses -> HideSuccesses -> Bool)
-> (HideSuccesses -> HideSuccesses -> Bool)
-> (HideSuccesses -> HideSuccesses -> HideSuccesses)
-> (HideSuccesses -> HideSuccesses -> HideSuccesses)
-> Ord 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
$cp1Ord :: Eq HideSuccesses
Ord, Typeable)
instance IsOption HideSuccesses where
  defaultValue :: HideSuccesses
defaultValue = Bool -> HideSuccesses
HideSuccesses Bool
False
  parseValue :: TestName -> Maybe HideSuccesses
parseValue = (Bool -> HideSuccesses) -> Maybe Bool -> Maybe HideSuccesses
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Bool -> HideSuccesses
HideSuccesses (Maybe Bool -> Maybe HideSuccesses)
-> (TestName -> Maybe Bool) -> TestName -> Maybe HideSuccesses
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TestName -> Maybe Bool
safeReadBool
  optionName :: Tagged HideSuccesses TestName
optionName = TestName -> Tagged HideSuccesses TestName
forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"hide-successes"
  optionHelp :: Tagged HideSuccesses TestName
optionHelp = TestName -> Tagged HideSuccesses TestName
forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"Do not print tests that passed successfully"
  optionCLParser :: Parser HideSuccesses
optionCLParser = Mod FlagFields HideSuccesses
-> HideSuccesses -> Parser HideSuccesses
forall v. IsOption v => Mod FlagFields v -> v -> Parser v
mkFlagCLParser Mod FlagFields HideSuccesses
forall a. Monoid a => a
mempty (Bool -> HideSuccesses
HideSuccesses Bool
True)

-- | 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
(UseColor -> UseColor -> Bool)
-> (UseColor -> UseColor -> Bool) -> Eq UseColor
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
Eq UseColor
-> (UseColor -> UseColor -> Ordering)
-> (UseColor -> UseColor -> Bool)
-> (UseColor -> UseColor -> Bool)
-> (UseColor -> UseColor -> Bool)
-> (UseColor -> UseColor -> Bool)
-> (UseColor -> UseColor -> UseColor)
-> (UseColor -> UseColor -> UseColor)
-> Ord 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
$cp1Ord :: Eq UseColor
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 = TestName -> Tagged UseColor TestName
forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"color"
  optionHelp :: Tagged UseColor TestName
optionHelp = TestName -> Tagged UseColor TestName
forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"When to use colored output"
  optionCLParser :: Parser UseColor
optionCLParser = Mod OptionFields UseColor -> Parser UseColor
forall v. IsOption v => Mod OptionFields v -> Parser v
mkOptionCLParser (Mod OptionFields UseColor -> Parser UseColor)
-> Mod OptionFields UseColor -> Parser UseColor
forall a b. (a -> b) -> a -> b
$ TestName -> Mod OptionFields UseColor
forall (f :: * -> *) a. HasMetavar f => TestName -> Mod f a
metavar TestName
"never|always|auto"
  showDefaultValue :: UseColor -> Maybe TestName
showDefaultValue = TestName -> Maybe TestName
forall a. a -> Maybe a
Just (TestName -> Maybe TestName)
-> (UseColor -> TestName) -> UseColor -> Maybe TestName
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/feuerbach/tasty/issues/152>
--
-- * <https://github.com/feuerbach/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.
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 = (Bool -> AnsiTricks) -> Maybe Bool -> Maybe AnsiTricks
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Bool -> AnsiTricks
AnsiTricks (Maybe Bool -> Maybe AnsiTricks)
-> (TestName -> Maybe Bool) -> TestName -> Maybe AnsiTricks
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TestName -> Maybe Bool
safeReadBool
  optionName :: Tagged AnsiTricks TestName
optionName = TestName -> Tagged AnsiTricks TestName
forall (m :: * -> *) a. Monad m => a -> m a
return TestName
"ansi-tricks"
  optionHelp :: Tagged AnsiTricks TestName
optionHelp = TestName -> Tagged AnsiTricks TestName
forall (m :: * -> *) a. Monad m => a -> m a
return (TestName -> Tagged AnsiTricks TestName)
-> TestName -> Tagged AnsiTricks TestName
forall a b. (a -> b) -> a -> b
$
    -- Multiline literals don't work because of -XCPP.
    TestName
"Enable various ANSI terminal tricks. " TestName -> TestName -> TestName
forall a. [a] -> [a] -> [a]
++
    TestName
"Can be set to 'true' or 'false'."
  showDefaultValue :: AnsiTricks -> Maybe TestName
showDefaultValue = TestName -> Maybe TestName
forall a. a -> Maybe a
Just (TestName -> Maybe TestName)
-> (AnsiTricks -> TestName) -> AnsiTricks -> Maybe TestName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> TestName
displayBool (Bool -> TestName)
-> (AnsiTricks -> Bool) -> AnsiTricks -> TestName
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 (Char -> Char) -> TestName -> TestName
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower TestName
s of
    TestName
"never"  -> UseColor -> Maybe UseColor
forall (m :: * -> *) a. Monad m => a -> m a
return UseColor
Never
    TestName
"always" -> UseColor -> Maybe UseColor
forall (m :: * -> *) a. Monad m => a -> m a
return UseColor
Always
    TestName
"auto"   -> UseColor -> Maybe UseColor
forall (m :: * -> *) a. Monad m => a -> m a
return UseColor
Auto
    TestName
_        -> Maybe UseColor
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 =
  STM Result -> IO Result
forall a. STM a -> IO a
atomically (STM Result -> IO Result) -> STM Result -> IO Result
forall a b. (a -> b) -> a -> b
$ do
    Status
status <- TVar Status -> STM Status
forall a. TVar a -> STM a
readTVar TVar Status
var
    case Status
status of
      Done Result
r -> Result -> STM Result
forall (m :: * -> *) a. Monad m => a -> m a
return Result
r
      Status
_ -> STM Result
forall a. STM a
retry

-- }}}

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

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

indent :: Int -> String
indent :: Int -> TestName
indent Int
n = Int -> Char -> TestName
forall a. Int -> a -> [a]
replicate (Int
indentSize Int -> Int -> Int
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 = TestName -> TestName
forall a. [a] -> [a]
reverse (TestName -> TestName)
-> (TestName -> TestName) -> TestName -> TestName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> TestName -> TestName
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\n') (TestName -> TestName)
-> (TestName -> TestName) -> TestName -> TestName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TestName -> TestName
forall a. [a] -> [a]
reverse (TestName -> TestName) -> TestName -> TestName
forall a b. (a -> b) -> a -> b
$ TestName
desc

    multiline :: Bool
multiline = Char
'\n' Char -> TestName -> Bool
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 = ((Char -> TestName) -> TestName -> TestName)
-> TestName -> (Char -> TestName) -> TestName
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Char -> TestName) -> TestName -> TestName
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap TestName
chomped ((Char -> TestName) -> TestName) -> (Char -> TestName) -> TestName
forall a b. (a -> b) -> a -> b
$ \Char
c ->
      if Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\n'
        then Char
c Char -> TestName -> TestName
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 => Monoid (Maximum a) where
  mempty :: Maximum a
mempty = Maximum a
forall a. Maximum a
MinusInfinity

  Maximum a
a mappend :: Maximum a -> Maximum a -> Maximum a
`mappend` Maximum a
b = a -> Maximum a
forall a. a -> Maximum a
Maximum (a
a a -> a -> a
forall a. Ord a => a -> a -> a
`max` a
b)
  Maximum a
MinusInfinity `mappend` Maximum a
a = Maximum a
a
  Maximum a
a `mappend` Maximum a
MinusInfinity = Maximum a
a
instance Ord a => Semigroup (Maximum a) where
  <> :: Maximum a -> Maximum a -> Maximum a
(<>) = Maximum a -> Maximum a -> Maximum a
forall a. Monoid a => a -> a -> a
mappend

-- | Compute the amount of space needed to align \"OK\"s and \"FAIL\"s
computeAlignment :: OptionSet -> TestTree -> Int
computeAlignment :: OptionSet -> TestTree -> Int
computeAlignment OptionSet
opts =
  (Int -> Maximum Int) -> Int
forall t p. (Num t, Num p) => (t -> Maximum p) -> p
fromMonoid ((Int -> Maximum Int) -> Int)
-> (TestTree -> Int -> Maximum Int) -> TestTree -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  TreeFold (Int -> Maximum Int)
-> OptionSet -> TestTree -> Int -> Maximum Int
forall b. Monoid b => TreeFold b -> OptionSet -> TestTree -> b
foldTestTree
    TreeFold (Int -> Maximum Int)
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 -> Int -> Maximum Int
forall a. a -> Maximum a
Maximum (TestName -> Int
stringWidth TestName
name Int -> Int -> Int
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 -> Int -> Maximum Int
m (Int -> Maximum Int) -> (Int -> Int) -> Int -> Maximum Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
indentSize)
      }
    OptionSet
opts
  where
    fromMonoid :: (t -> Maximum p) -> p
fromMonoid t -> Maximum p
m =
      case t -> Maximum p
m t
0 of
        Maximum p
MinusInfinity -> p
0
        Maximum p
x -> p
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 VERSION_wcwidth
stringWidth :: TestName -> Int
stringWidth = [Int] -> Int
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
Prelude.sum ([Int] -> Int) -> (TestName -> [Int]) -> TestName -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Int) -> TestName -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Int
charWidth
 where charWidth :: Char -> Int
charWidth Char
c = case Char -> Int
wcwidth Char
c of
        -1 -> Int
1  -- many chars have "undefined" width; default to 1 for these.
        Int
w  -> Int
w
#else
stringWidth = length
#endif

-- (Potentially) colorful output
ok, fail, skipped, infoOk, infoFail :: (?colors :: Bool) => String -> IO ()
fail :: TestName -> IO ()
fail     = (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
failFormat
ok :: TestName -> IO ()
ok       = (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
okFormat
skipped :: TestName -> IO ()
skipped  = (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
skippedFormat
infoOk :: TestName -> IO ()
infoOk   = (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
infoOkFormat
infoFail :: TestName -> IO ()
infoFail = (?colors::Bool) => ConsoleFormat -> TestName -> IO ()
ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
infoFailFormat

output
  :: (?colors :: Bool)
  => ConsoleFormat
  -> String
  -> IO ()
output :: ConsoleFormat -> TestName -> IO ()
output ConsoleFormat
format = (?colors::Bool) => ConsoleFormatPrinter
ConsoleFormatPrinter
withConsoleFormat ConsoleFormat
format (IO () -> IO ()) -> (TestName -> IO ()) -> TestName -> IO ()
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 :: ConsoleFormatPrinter
withConsoleFormat ConsoleFormat
format IO ()
action
  | ?colors::Bool
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
    ) IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO a
`finally` [SGR] -> IO ()
setSGR []
  | Bool
otherwise = IO ()
action

-- }}}