packages feed

tasty-falsify-0.1.0: src/Test/Tasty/Falsify/CLI.hs

{-# OPTIONS_GHC -Wno-orphans #-}

-- | Command line flags
module Test.Tasty.Falsify.CLI (
    Tests(..)
  , MaxShrinks(..)
  , Replay(..)
  , MaxRatio(..)
  , extractFalsifyCliFlags
  , allFalsifyCliFlags
  ) where

import Data.Default
import Data.Tagged
import Test.Tasty.Options (IsOption(..))
import Data.Proxy

import qualified Options.Applicative as Opts
import qualified Test.Tasty.Options  as Tasty

import Test.Falsify.Driver (ReplaySeed, parseReplaySeed)
import qualified Test.Falsify.Driver as Falsify

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

newtype Tests      = Tests      { getTests      :: Word             }
newtype MaxShrinks = MaxShrinks { getMaxShrinks :: Maybe Word       }
newtype Replay     = Replay     { getReplay     :: Maybe ReplaySeed }
newtype MaxRatio   = MaxRatio   { getMaxRatio   :: Word             }

-- | Extract CLI options
extractFalsifyCliFlags :: Tasty.OptionSet -> (Falsify.Verbose, Falsify.Options)
extractFalsifyCliFlags optionSet = (
      Tasty.lookupOption optionSet
    , Falsify.Options {
          tests      = getTests      $ Tasty.lookupOption optionSet
        , maxShrinks = getMaxShrinks $ Tasty.lookupOption optionSet
        , replay     = getReplay     $ Tasty.lookupOption optionSet
        , maxRatio   = getMaxRatio   $ Tasty.lookupOption optionSet
        }
    )

-- | Make @tasty@ aware of the @falsify@ specific CLI flags
allFalsifyCliFlags :: [Tasty.OptionDescription]
allFalsifyCliFlags = [
      Tasty.Option $ Proxy @Falsify.Verbose
    , Tasty.Option $ Proxy @Tests
    , Tasty.Option $ Proxy @MaxShrinks
    , Tasty.Option $ Proxy @Replay
    , Tasty.Option $ Proxy @MaxRatio
    ]

{-------------------------------------------------------------------------------
  'IsOption' instances
-------------------------------------------------------------------------------}

instance IsOption Tests where
  defaultValue   = Tests (Falsify.tests def)
  parseValue     = fmap Tests . Tasty.safeRead . filter (/= '_')
  optionName     = Tagged "falsify-tests"
  optionHelp     = Tagged "Number of test cases to generate"

instance IsOption MaxShrinks where
  defaultValue   = MaxShrinks (Falsify.maxShrinks def)
  parseValue     = fmap (MaxShrinks . Just) . Tasty.safeRead
  optionName     = Tagged "falsify-shrinks"
  optionHelp     = Tagged "Random seed to use for replaying a previous test run"

instance IsOption Replay where
  defaultValue   = Replay (Falsify.replay def)
  parseValue     = aux . parseReplaySeed
    where
      -- Slightly confusing here: the /outer/ 'Maybe' corresponds to test
      -- failure, the /inner/ 'Maybe' to the fact that the seed is optional.
      aux :: Either String ReplaySeed -> Maybe Replay
      aux (Left  _err) = Nothing
      aux (Right seed) = Just $ Replay (Just seed)
  optionName     = Tagged "falsify-replay"
  optionHelp     = Tagged "Random seed to use for replaying test"
  optionCLParser = Opts.option (Opts.str >>= aux . parseReplaySeed) $ mconcat [
                       Opts.long $ untag $ optionName @Replay
                     , Opts.help $ untag $ optionHelp @Replay
                     ]
    where
      aux :: Either String ReplaySeed -> Opts.ReadM Replay
      aux (Left  err)  = fail err
      aux (Right seed) = return $ Replay (Just seed)

instance IsOption MaxRatio where
  defaultValue   = MaxRatio (Falsify.maxRatio def)
  parseValue     = fmap MaxRatio . Tasty.safeRead . filter (/= '_')
  optionName     = Tagged "falsify-max-ratio"
  optionHelp     = Tagged "Maximum number of discarded tests per successful test"

instance IsOption Falsify.Verbose where
  defaultValue   = Falsify.NotVerbose
  parseValue     = fmap ( \b -> if b then Falsify.Verbose
                                     else Falsify.NotVerbose
                        )
                 . Tasty.safeReadBool
  optionName     = Tagged $ "falsify-verbose"
  optionHelp     = Tagged $ "Show the generated test cases"
  optionCLParser = Tasty.mkFlagCLParser mempty Falsify.Verbose