packages feed

skeletest-0.4.0: src/Skeletest/Main.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Skeletest.Main (
  runSkeletest,

  -- * CLI flags
  Flag,
  flag,

  -- * Snapshots
  SnapshotRenderer (..),
  renderWithShow,

  -- * Plugins
  Plugin,

  -- * Re-exports
  Spec,
) where

import Control.Monad (when)
import Data.Foldable (traverse_)
import Skeletest.Internal.CLI (
  ANSIFlag (..),
  Flag,
  FormatFlag,
  flag,
  getFlag,
  loadCliArgs,
 )
import Skeletest.Internal.Capture (CaptureOutputFlag (..), captureOutputPlugin)
import Skeletest.Internal.Exit (TestExitCode (..), exitWith, handleUnknownErrors)
import Skeletest.Internal.Hooks (
  ModifySpecRegistryHookContext (..),
  RunSpecsHookContext (..),
  setUserHooks,
  userHooks,
 )
import Skeletest.Internal.Snapshot (
  SnapshotRenderer (..),
  renderWithShow,
  setSnapshotRenderers,
  snapshotPlugin,
 )
import Skeletest.Internal.Spec (
  Spec,
  SpecInfo (..),
  newSpecRunner,
  specTreePlugin,
 )
import Skeletest.Internal.Spec.Tree (getSpecTests)
import Skeletest.Internal.Utils.Color qualified as Color
import Skeletest.Internal.Utils.Term qualified as Term
import Skeletest.Plugin (Hooks (..), Plugin (..))
import Skeletest.Prop.Internal (propPlugin)

runSkeletest :: [Plugin] -> [(FilePath, Spec)] -> IO ()
runSkeletest userPlugins testModules = handleUnknownErrors $ do
  Term.init
  selections <- loadCliArgs builtinFlags cliFlags
  resolveANSISupport

  setSnapshotRenderers snapshotRenderers
  setUserHooks hooks

  let initialSpecs = map mkSpec testModules
  specs <-
    userHooks.modifySpecRegistry
      ModifySpecRegistryHookContext
        { testTargets = selections
        }
      initialSpecs
      pure
  when (null $ concatMap (getSpecTests . (.spec)) specs) $ do
    Term.outputErr $ Color.red "ERROR: No tests selected!"
    exitWith ExitNoTests

  runner <- newSpecRunner initialSpecs
  exitCode <-
    userHooks.runSpecs
      RunSpecsHookContext
      specs
      runner.run
  runner.printSummary
  exitWith exitCode
 where
  builtinPlugins =
    [ specTreePlugin
    , snapshotPlugin
    , captureOutputPlugin
    , propPlugin
    ]

  hooks = foldMap (.hooks) $ builtinPlugins <> userPlugins
  snapshotRenderers = foldMap (.snapshotRenderers) $ builtinPlugins <> userPlugins

  cliFlags = foldMap (.cliFlags) userPlugins
  builtinFlags = foldMap (.cliFlags) builtinPlugins <> generalFlags
  generalFlags =
    [ flag @ANSIFlag
    , flag @(Maybe FormatFlag)
    ]

  mkSpec (specPath, spec) =
    SpecInfo
      { specPath
      , spec
      }

resolveANSISupport :: IO ()
resolveANSISupport = do
  CaptureOutputFlag captureOutput <- getFlag
  ANSIFlag mUseANSI <- getFlag
  traverse_ Term.setANSISupport $
    if
      | Just userANSI <- mUseANSI -> Just userANSI
      | not captureOutput -> Just False -- if --capture-output=off, ANSI could mess up output
      | otherwise -> Nothing