    -- | This module contains the core definitions related to ingredients.
    --
    -- Ingredients themselves are provided by other modules (usually under
    -- the @Test.Tasty.Ingredients.*@ hierarchy).
    --
    -- @since 0.8
    module Test.Tasty.Ingredients
      ( Ingredient(..)
      , tryIngredients
      , ingredientOptions
      , ingredientsOptions
      , suiteOptions
      , composeReporters
      ) where

    import Control.Monad
    import Data.Proxy
    import qualified Data.Foldable as F

    import Test.Tasty.Core
    import Test.Tasty.Run
    import Test.Tasty.Options
    import Test.Tasty.Options.Core
    import Control.Concurrent.Async (concurrently)

    -- | 'Ingredient's make your test suite tasty.
    --
    -- Ingredients represent different actions that you can perform on your
    -- test suite. One obvious ingredient that you want to include is
    -- one that runs tests and reports the progress and results.
    --
    -- Another standard ingredient is one that simply prints the names of all
    -- tests.
    --
    -- Similar to test providers (see 'IsTest'), every ingredient may specify
    -- which options it cares about, so that those options are presented to
    -- the user if the ingredient is included in the test suite.
    --
    -- An ingredient can choose, typically based on the 'OptionSet', whether to
    -- run. That's what the 'Maybe' is for. The first ingredient that agreed to
    -- run does its work, and the remaining ingredients are ignored. Thus, the
    -- order in which you arrange the ingredients may matter.
    --
    -- Usually, the ingredient which runs the tests is unconditional and thus
    -- should be placed last in the list. Other ingredients usually run only
    -- if explicitly requested via an option. Their relative order thus doesn't
    -- matter.
    --
    -- That's all you need to know from an (advanced) user perspective. Read
    -- on if you want to create a new ingredient.
    --
    -- There are two kinds of ingredients.
    --
    -- The first kind is 'TestReporter'. If the ingredient that agrees to run
    -- is a 'TestReporter', then tasty will automatically launch the tests and
    -- pass a 'StatusMap' to the ingredient. All the ingredient needs to do
    -- then is to process the test results and probably report them to the user
    -- in some way (hence the name).
    --
    -- 'TestManager' is the second kind of ingredient. It is typically used for
    -- test management purposes (such as listing the test names), although it
    -- can also be used for running tests (but, unlike 'TestReporter', it has
    -- to launch the tests manually if it wants them to be run).  It is
    -- therefore more general than 'TestReporter'. 'TestReporter' is provided
    -- just for convenience.
    --
    -- The function's result should indicate whether all the tests passed.
    --
    -- In the 'TestManager' case, it's up to the ingredient author to decide
    -- what the result should be. When no tests are run, the result should
    -- probably be 'True'. Sometimes, even if some tests run and fail, it still
    -- makes sense to return 'True'.
    --
    -- @since 0.4
    data Ingredient
      = TestReporter
          [OptionDescription]
          (OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool)))
       -- ^ For the explanation on how the callback works, see the
       -- documentation for 'launchTestTree'.
       --
       -- @since 0.10
      | TestManager
          [OptionDescription]
          (OptionSet -> TestTree -> Maybe (IO Bool))
       -- ^ @since 0.4

    -- | Try to run an 'Ingredient'.
    --
    -- If the ingredient refuses to run (usually based on the 'OptionSet'),
    -- the function returns 'Nothing'.
    --
    -- For a 'TestReporter', this function automatically starts running the
    -- tests in the background.
    --
    -- This function is not publicly exposed.
    tryIngredient :: Ingredient -> OptionSet -> TestTree -> Maybe (IO Bool)
    tryIngredient :: Ingredient -> OptionSet -> TestTree -> Maybe (IO Bool)
    tryIngredient (TestReporter [OptionDescription]
    _ OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool))
    report) OptionSet
    opts TestTree
    testTree = do -- Maybe monad
      reportFn <- OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool))
    report OptionSet
    opts TestTree
    testTree
      return $ launchTestTree opts testTree $ \StatusMap
    smap -> StatusMap -> IO (Time -> IO Bool)
    reportFn StatusMap
    smap
    tryIngredient (TestManager [OptionDescription]
    _ OptionSet -> TestTree -> Maybe (IO Bool)
    manage) OptionSet
    opts TestTree
    testTree =
      OptionSet -> TestTree -> Maybe (IO Bool)
    manage OptionSet
    opts TestTree
    testTree

    -- | Run the first 'Ingredient' that agrees to be run.
    --
    -- If no one accepts the task, return 'Nothing'. This is usually a sign of
    -- misconfiguration.
    --
    -- @since 0.4
    tryIngredients :: [Ingredient] -> OptionSet -> TestTree -> Maybe (IO Bool)
    tryIngredients :: [Ingredient] -> OptionSet -> TestTree -> Maybe (IO Bool)
    tryIngredients [Ingredient]
    ins OptionSet
    opts' TestTree
    tree' =
      [Maybe (IO Bool)] -> Maybe (IO Bool)
    forall (t :: * -> *) (m :: * -> *) a.
    (Foldable t, MonadPlus m) =>
    t (m a) -> m a
    msum ([Maybe (IO Bool)] -> Maybe (IO Bool))
    -> [Maybe (IO Bool)] -> Maybe (IO Bool)
    forall a b. (a -> b) -> a -> b
    $ (Ingredient -> Maybe (IO Bool))
    -> [Ingredient] -> [Maybe (IO Bool)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\Ingredient
    i -> Ingredient -> OptionSet -> TestTree -> Maybe (IO Bool)
    tryIngredient Ingredient
    i OptionSet
    opts TestTree
    tree) [Ingredient]
    ins
      where
        (OptionSet
    opts, TestTree
    tree) = OptionSet -> TestTree -> (OptionSet, TestTree)
    applyTopLevelPlusTestOptions OptionSet
    opts' TestTree
    tree'

    -- | Return the options which are relevant for the given ingredient.
    --
    -- Note that this isn't the same as simply pattern-matching on
    -- 'Ingredient'. E.g. options for a 'TestReporter' automatically include
    -- 'NumThreads'.
    --
    -- @since 0.4
    ingredientOptions :: Ingredient -> [OptionDescription]
    ingredientOptions :: Ingredient -> [OptionDescription]
    ingredientOptions (TestReporter [OptionDescription]
    opts OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool))
    _) =
      Proxy NumThreads -> OptionDescription
    forall v. IsOption v => Proxy v -> OptionDescription
    Option (Proxy NumThreads
    forall {k} (t :: k). Proxy t
    Proxy :: Proxy NumThreads) OptionDescription -> [OptionDescription] -> [OptionDescription]
    forall a. a -> [a] -> [a]
    : [OptionDescription]
    opts
    ingredientOptions (TestManager [OptionDescription]
    opts OptionSet -> TestTree -> Maybe (IO Bool)
    _) = [OptionDescription]
    opts

    -- | Like 'ingredientOptions', but folds over multiple ingredients.
    --
    -- @since 0.4
    ingredientsOptions :: [Ingredient] -> [OptionDescription]
    ingredientsOptions :: [Ingredient] -> [OptionDescription]
    ingredientsOptions = [OptionDescription] -> [OptionDescription]
    uniqueOptionDescriptions ([OptionDescription] -> [OptionDescription])
    -> ([Ingredient] -> [OptionDescription])
    -> [Ingredient]
    -> [OptionDescription]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Ingredient -> [OptionDescription])
    -> [Ingredient] -> [OptionDescription]
    forall m a. Monoid m => (a -> m) -> [a] -> m
    forall (t :: * -> *) m a.
    (Foldable t, Monoid m) =>
    (a -> m) -> t a -> m
    F.foldMap Ingredient -> [OptionDescription]
    ingredientOptions

    -- | All the options relevant for this test suite. This includes the
    -- options for the test tree and ingredients, and the core options.
    --
    -- @since 0.4
    suiteOptions :: [Ingredient] -> TestTree -> [OptionDescription]
    suiteOptions :: [Ingredient] -> TestTree -> [OptionDescription]
    suiteOptions [Ingredient]
    ins TestTree
    tree = [OptionDescription] -> [OptionDescription]
    uniqueOptionDescriptions ([OptionDescription] -> [OptionDescription])
    -> [OptionDescription] -> [OptionDescription]
    forall a b. (a -> b) -> a -> b
    $
      [OptionDescription]
    coreOptions [OptionDescription] -> [OptionDescription] -> [OptionDescription]
    forall a. [a] -> [a] -> [a]
    ++
      [Ingredient] -> [OptionDescription]
    ingredientsOptions [Ingredient]
    ins [OptionDescription] -> [OptionDescription] -> [OptionDescription]
    forall a. [a] -> [a] -> [a]
    ++
      TestTree -> [OptionDescription]
    treeOptions TestTree
    tree

    -- | Compose two 'TestReporter' ingredients which are then executed
    -- in parallel. This can be useful if you want to have two reporters
    -- active at the same time, e.g., one which prints to the console and
    -- one which writes the test results to a file.
    --
    -- Be aware that it is not possible to use 'composeReporters' with a 'TestManager',
    -- it only works for 'TestReporter' ingredients.
    --
    -- @since 0.11.2
    composeReporters :: Ingredient -> Ingredient -> Ingredient
    composeReporters :: Ingredient -> Ingredient -> Ingredient
    composeReporters (TestReporter [OptionDescription]
    o1 OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool))
    f1) (TestReporter [OptionDescription]
    o2 OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool))
    f2) =
      [OptionDescription]
    -> (OptionSet
        -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool)))
    -> Ingredient
    TestReporter ([OptionDescription]
    o1 [OptionDescription] -> [OptionDescription] -> [OptionDescription]
    forall a. [a] -> [a] -> [a]
    ++ [OptionDescription]
    o2) ((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
    o TestTree
    t ->
      case (OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool))
    f1 OptionSet
    o TestTree
    t, OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool))
    f2 OptionSet
    o TestTree
    t) of
        (Maybe (StatusMap -> IO (Time -> IO Bool))
    g, Maybe (StatusMap -> IO (Time -> IO Bool))
    Nothing) -> Maybe (StatusMap -> IO (Time -> IO Bool))
    g
        (Maybe (StatusMap -> IO (Time -> IO Bool))
    Nothing, Maybe (StatusMap -> IO (Time -> IO Bool))
    g) -> Maybe (StatusMap -> IO (Time -> IO Bool))
    g
        (Just StatusMap -> IO (Time -> IO Bool)
    g1, Just StatusMap -> IO (Time -> IO Bool)
    g2) -> (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
    s -> do
          (h1, h2) <- IO (Time -> IO Bool)
    -> IO (Time -> IO Bool) -> IO (Time -> IO Bool, Time -> IO Bool)
    forall a b. IO a -> IO b -> IO (a, b)
    concurrently (StatusMap -> IO (Time -> IO Bool)
    g1 StatusMap
    s) (StatusMap -> IO (Time -> IO Bool)
    g2 StatusMap
    s)
          return $ \Time
    x -> ((Bool, Bool) -> Bool) -> IO (Bool, Bool) -> IO Bool
    forall a b. (a -> b) -> IO a -> IO b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap ((Bool -> Bool -> Bool) -> (Bool, Bool) -> Bool
    forall a b c. (a -> b -> c) -> (a, b) -> c
    uncurry Bool -> Bool -> Bool
    (&&)) (IO (Bool, Bool) -> IO Bool) -> IO (Bool, Bool) -> IO Bool
    forall a b. (a -> b) -> a -> b
    $ IO Bool -> IO Bool -> IO (Bool, Bool)
    forall a b. IO a -> IO b -> IO (a, b)
    concurrently (Time -> IO Bool
    h1 Time
    x) (Time -> IO Bool
    h2 Time
    x)
    composeReporters Ingredient
    _ Ingredient
    _ = [Char] -> Ingredient
    forall a. HasCallStack => [Char] -> a
    error [Char]
    "Only TestReporters can be composed"