packages feed

Cabal-3.12.0.0: src/Distribution/Simple/Setup/Test.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}

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

-- |
-- Module      :  Distribution.Simple.Test
-- Copyright   :  Isaac Jones 2003-2004
--                Duncan Coutts 2007
-- License     :  BSD3
--
-- Maintainer  :  cabal-devel@haskell.org
-- Portability :  portable
--
-- Definition of the testing command-line options.
-- See: @Distribution.Simple.Setup@
module Distribution.Simple.Setup.Test
  ( TestFlags (..)
  , emptyTestFlags
  , defaultTestFlags
  , testCommand
  , TestShowDetails (..)
  , testOptions'
  ) where

import Distribution.Compat.Prelude hiding (get)
import Prelude ()

import qualified Distribution.Compat.CharParsing as P
import Distribution.Parsec
import Distribution.Pretty
import Distribution.ReadE
import Distribution.Simple.Command hiding (boolOpt, boolOpt')
import Distribution.Simple.Flag
import Distribution.Simple.InstallDirs
import Distribution.Simple.Utils
import Distribution.Verbosity
import qualified Text.PrettyPrint as Disp

import Distribution.Simple.Setup.Common

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

-- * Test flags

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

data TestShowDetails = Never | Failures | Always | Streaming | Direct
  deriving (Eq, Ord, Enum, Bounded, Generic, Show, Typeable)

instance Binary TestShowDetails
instance Structured TestShowDetails

knownTestShowDetails :: [TestShowDetails]
knownTestShowDetails = [minBound .. maxBound]

instance Pretty TestShowDetails where
  pretty = Disp.text . lowercase . show

instance Parsec TestShowDetails where
  parsec = maybe (fail "invalid TestShowDetails") return . classify =<< ident
    where
      ident = P.munch1 (\c -> isAlpha c || c == '_' || c == '-')
      classify str = lookup (lowercase str) enumMap
      enumMap :: [(String, TestShowDetails)]
      enumMap =
        [ (prettyShow x, x)
        | x <- knownTestShowDetails
        ]

-- TODO: do we need this instance?
instance Monoid TestShowDetails where
  mempty = Never
  mappend = (<>)

instance Semigroup TestShowDetails where
  a <> b = if a < b then b else a

data TestFlags = TestFlags
  { testDistPref :: Flag FilePath
  , testVerbosity :: Flag Verbosity
  , testHumanLog :: Flag PathTemplate
  , testMachineLog :: Flag PathTemplate
  , testShowDetails :: Flag TestShowDetails
  , testKeepTix :: Flag Bool
  , testWrapper :: Flag FilePath
  , testFailWhenNoTestSuites :: Flag Bool
  , -- TODO: think about if/how options are passed to test exes
    testOptions :: [PathTemplate]
  }
  deriving (Show, Generic, Typeable)

instance Binary TestFlags
instance Structured TestFlags

defaultTestFlags :: TestFlags
defaultTestFlags =
  TestFlags
    { testDistPref = NoFlag
    , testVerbosity = Flag normal
    , testHumanLog = toFlag $ toPathTemplate $ "$pkgid-$test-suite.log"
    , testMachineLog = toFlag $ toPathTemplate $ "$pkgid.log"
    , testShowDetails = toFlag Direct
    , testKeepTix = toFlag False
    , testWrapper = NoFlag
    , testFailWhenNoTestSuites = toFlag False
    , testOptions = []
    }

testCommand :: CommandUI TestFlags
testCommand =
  CommandUI
    { commandName = "test"
    , commandSynopsis =
        "Run all/specific tests in the test suite."
    , commandDescription = Just $ \_pname ->
        wrapText $
          testOrBenchmarkHelpText "test"
    , commandNotes = Nothing
    , commandUsage =
        usageAlternatives
          "test"
          [ "[FLAGS]"
          , "TESTCOMPONENTS [FLAGS]"
          ]
    , commandDefaultFlags = defaultTestFlags
    , commandOptions = testOptions'
    }

testOptions' :: ShowOrParseArgs -> [OptionField TestFlags]
testOptions' showOrParseArgs =
  [ optionVerbosity testVerbosity (\v flags -> flags{testVerbosity = v})
  , optionDistPref
      testDistPref
      (\d flags -> flags{testDistPref = d})
      showOrParseArgs
  , option
      []
      ["log"]
      ( "Log all test suite results to file (name template can use "
          ++ "$pkgid, $compiler, $os, $arch, $test-suite, $result)"
      )
      testHumanLog
      (\v flags -> flags{testHumanLog = v})
      ( reqArg'
          "TEMPLATE"
          (toFlag . toPathTemplate)
          (flagToList . fmap fromPathTemplate)
      )
  , option
      []
      ["machine-log"]
      ( "Produce a machine-readable log file (name template can use "
          ++ "$pkgid, $compiler, $os, $arch, $result)"
      )
      testMachineLog
      (\v flags -> flags{testMachineLog = v})
      ( reqArg'
          "TEMPLATE"
          (toFlag . toPathTemplate)
          (flagToList . fmap fromPathTemplate)
      )
  , option
      []
      ["show-details"]
      ( "'always': always show results of individual test cases. "
          ++ "'never': never show results of individual test cases. "
          ++ "'failures': show results of failing test cases. "
          ++ "'streaming': show results of test cases in real time."
          ++ "'direct': send results of test cases in real time; no log file."
      )
      testShowDetails
      (\v flags -> flags{testShowDetails = v})
      ( reqArg
          "FILTER"
          ( parsecToReadE
              ( \_ ->
                  "--show-details flag expects one of "
                    ++ intercalate
                      ", "
                      (map prettyShow knownTestShowDetails)
              )
              (fmap toFlag parsec)
          )
          (flagToList . fmap prettyShow)
      )
  , option
      []
      ["keep-tix-files"]
      "keep .tix files for HPC between test runs"
      testKeepTix
      (\v flags -> flags{testKeepTix = v})
      trueArg
  , option
      []
      ["test-wrapper"]
      "Run test through a wrapper."
      testWrapper
      (\v flags -> flags{testWrapper = v})
      ( reqArg'
          "FILE"
          (toFlag :: FilePath -> Flag FilePath)
          (flagToList :: Flag FilePath -> [FilePath])
      )
  , option
      []
      ["fail-when-no-test-suites"]
      ("Exit with failure when no test suites are found.")
      testFailWhenNoTestSuites
      (\v flags -> flags{testFailWhenNoTestSuites = v})
      trueArg
  , option
      []
      ["test-options"]
      ( "give extra options to test executables "
          ++ "(name templates can use $pkgid, $compiler, "
          ++ "$os, $arch, $test-suite)"
      )
      testOptions
      (\v flags -> flags{testOptions = v})
      ( reqArg'
          "TEMPLATES"
          (map toPathTemplate . splitArgs)
          (const [])
      )
  , option
      []
      ["test-option"]
      ( "give extra option to test executables "
          ++ "(no need to quote options containing spaces, "
          ++ "name template can use $pkgid, $compiler, "
          ++ "$os, $arch, $test-suite)"
      )
      testOptions
      (\v flags -> flags{testOptions = v})
      ( reqArg'
          "TEMPLATE"
          (\x -> [toPathTemplate x])
          (map fromPathTemplate)
      )
  ]

emptyTestFlags :: TestFlags
emptyTestFlags = mempty

instance Monoid TestFlags where
  mempty = gmempty
  mappend = (<>)

instance Semigroup TestFlags where
  (<>) = gmappend