content: {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE GADTs #-}
  {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE PatternSynonyms #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE TypeApplications #-}
  {-# OPTIONS_GHC -fno-warn-orphans #-}
  
  import           Control.Exception ( displayException, try, SomeException(..), fromException )
  import qualified Data.List as L
  import           Data.Parameterized.Context ( pattern Empty, pattern (:>) )
  import           Data.Parameterized.Some
  import           Data.Ratio ( (%) )
  import qualified Data.Set as Set
  import qualified Data.Text as T
  import           Data.Void
  import qualified Prettyprinter as PP
  
  import           Test.Tasty
  import           Test.Tasty.Checklist
  import           Test.Tasty.HUnit
  
  import           What4.BaseTypes
  import           What4.Concrete
  import           What4.Config
  
  
  testSetAndGet :: [TestTree]
  testSetAndGet =
    [
      testCase "create multiple options" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "optstr"
            o2 = configOption BaseIntegerRepr "optint"
            o3 = configOption BaseBoolRepr "optbool"
            o1' = mkOpt o1 stringOptSty Nothing Nothing
            o2' = mkOpt o2 integerOptSty Nothing Nothing
            o3' = mkOpt o3 boolOptSty Nothing Nothing
        extendConfig [o3', o2', o1'] cfg
  
    , testCase "create conflicting options" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "mainopt"
            o2 = configOption BaseIntegerRepr "mainopt"
            o1' = mkOpt o1 stringOptSty Nothing Nothing
            o2' = mkOpt o2 integerOptSty Nothing Nothing
        res <- try $ extendConfig [o2', o1'] cfg
        wantOptCreateFailure "already exists with type" res
  
    , testCase "create conflicting options at different levels" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "mainopt"
            o2 = configOption BaseIntegerRepr "main.mainopt"
            o1' = mkOpt o1 stringOptSty Nothing Nothing
            o2' = mkOpt o2 integerOptSty Nothing Nothing
        res <- try @SomeException $ extendConfig [o2', o1'] cfg
        case res of
          Right () -> return ()
          Left e -> assertFailure $ "Unexpected exception: " <> displayException e
  
    , testCase "create duplicate unicode options" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "mainopt"
            o2 = configOption (BaseStringRepr UnicodeRepr) "mainopt"
            o1' = mkOpt o1 stringOptSty Nothing Nothing
            o2' = mkOpt o2 stringOptSty Nothing Nothing
        res <- try @SomeException $ extendConfig [o2', o1'] cfg
        case res of
          Right () -> return ()
          Left e -> assertFailure $ "Unexpected exception: " <> displayException e
  
    , testCaseSteps "get unset value, no default" $ \step -> do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "optstr"
            o2 = configOption BaseIntegerRepr "optint"
            o3 = configOption BaseBoolRepr "optbool"
            o1' = mkOpt o1 stringOptSty Nothing Nothing
            o2' = mkOpt o2 integerOptSty Nothing Nothing
            o3' = mkOpt o3 boolOptSty Nothing Nothing
        extendConfig [o3', o2', o1'] cfg
        access1 <- getOptionSetting o1 cfg
        access2 <- getOptionSetting o2 cfg
        access3 <- getOptionSetting o3 cfg
  
        step "get unset string opt"
        v1 <- getMaybeOpt access1
        Nothing @=? v1
        res1 <- try $ getOpt access1
        wantOptGetFailure "not set" res1
  
        step "get unset integer opt"
        v2 <- getMaybeOpt access2
        Nothing @=? v2
        res2 <- try $ getOpt access2
        wantOptGetFailure "not set" res2
  
        step "get unset bool opt"
        v3 <- getMaybeOpt access3
        Nothing @=? v3
        res3 <- try $ getOpt access3
        wantOptGetFailure "not set" res3
  
    , testCaseSteps "get unset value, with default" $ \step -> do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "optstr"
            o2 = configOption BaseIntegerRepr "optint"
            o3 = configOption BaseBoolRepr "optbool"
            o1' = mkOpt o1 stringOptSty Nothing (Just $ ConcreteString "strval")
            o2' = mkOpt o2 integerOptSty Nothing (Just $ ConcreteInteger 11)
            o3' = mkOpt o3 boolOptSty Nothing (Just $ ConcreteBool True)
        extendConfig [o3', o2', o1'] cfg
        access1 <- getOptionSetting o1 cfg
        access2 <- getOptionSetting o2 cfg
        access3 <- getOptionSetting o3 cfg
        step "get unset default string opt"
        v1 <- getMaybeOpt access1
        Just "strval" @=? v1
        step "get unset default integer opt"
        v2 <- getMaybeOpt access2
        Just 11 @=? v2
        step "get unset default bool opt"
        v3 <- getMaybeOpt access3
        Just True @=? v3
  
    , testCaseSteps "get set value, with default" $ \step -> do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "optstr"
            o2 = configOption BaseIntegerRepr "optint"
            o3 = configOption BaseBoolRepr "optbool"
            o1' = mkOpt o1 stringOptSty Nothing (Just $ ConcreteString "strval")
            o2' = mkOpt o2 integerOptSty Nothing (Just $ ConcreteInteger 11)
            o3' = mkOpt o3 boolOptSty Nothing (Just $ ConcreteBool True)
        extendConfig [o3', o2', o1'] cfg
        access1 <- getOptionSetting o1 cfg
        access2 <- getOptionSetting o2 cfg
        access3 <- getOptionSetting o3 cfg
  
        step "set string opt"
        res1 <- setOpt access1 "flibberty"
        show <$> res1 @?= []
  
        step "set bool opt"
        res2 <- setOpt access3 False
        show <$> res2 @?= []
  
        step "set integer opt"
        res3 <- setOpt access2 9945
        show <$> res3 @?= []
  
        step "get string opt"
        v1 <- getMaybeOpt access1
        Just "flibberty" @=? v1
        step "get integer opt"
        v2 <- getMaybeOpt access2
        Just 9945 @=? v2
        step "get bool opt"
        v3 <- getMaybeOpt access3
        Just False @=? v3
  
    , testCaseSteps "set invalid values" $ \step -> do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "optstr"
            o2 = configOption BaseIntegerRepr "optint"
            o3 = configOption BaseRealRepr "optbool"
            -- n.b. the default values are not checked by the style!
            o1' = mkOpt o1 (enumOptSty
                            (Set.fromList ["eeny", "meeny", "miny", "mo" ]))
                  Nothing (Just $ ConcreteString "strval")
            o2' = mkOpt o2 (integerWithRangeOptSty Unbounded (Inclusive 10))
                  Nothing (Just $ ConcreteInteger 11)
            o3' = mkOpt o3 (realWithMinOptSty (Exclusive 1.23))
                  Nothing (Just $ ConcreteReal 0.0)
        extendConfig [o3', o2', o1'] cfg
        access1 <- getOptionSetting o1 cfg
        access2 <- getOptionSetting o2 cfg
        access3 <- getOptionSetting o3 cfg
  
        step "initial defaults"
        getMaybeOpt access1 >>= (@?= Just "strval")
        getMaybeOpt access2 >>= (@?= Just 11)
        getMaybeOpt access3 >>= (@?= Just (0 % 1 :: Rational))
  
        step "set string opt invalidly"
        -- Note: the strong typing prevents both of the following
        -- setOpt access1 32
        -- setOpt access1 False
        res1 <- try $ setOpt access1 "frobozz"
        wantOptSetFailure "invalid setting \"frobozz\"" res1
        wantOptSetFailure "eeny, meeny, miny, mo" res1
        (try @SomeException $ setOpt access1 "meeny") >>= \case
          Right [] -> return ()
          Right w -> assertFailure $ "Unexpected warnings: " <> show w
          Left e -> assertFailure $ "Unexpected exception: " <> displayException e
  
        step "set integer opt invalidly"
        wantOptSetFailure "out of range" =<< (try $ setOpt access2 11)
        wantOptSetFailure "expected integer value in (-∞, 10]" =<< (try $ setOpt access2 11)
        (try @SomeException $ setOpt access2 10) >>= \case
          Right [] -> return ()
          Right w -> assertFailure $ "Unexpected warnings: " <> show w
          Left e -> assertFailure $ "Unexpected exception: " <> displayException e
        (try @SomeException $ setOpt access2 (-3)) >>= \case
          Right [] -> return ()
          Right w -> assertFailure $ "Unexpected warnings: " <> show w
          Left e -> assertFailure $ "Unexpected exception: " <> displayException e
  
        step "set real opt invalidly"
        wantOptSetFailure "out of range" =<< (try $ setOpt access3 (0 % 3))
        wantOptSetFailure "expected real value in (123 % 100, +∞)"
          =<< (try $ setOpt access3 (0 % 3))
        wantOptSetFailure "out of range" =<< (try $ setOpt access3 (1229 % 1000))
        wantOptSetFailure "out of range" =<< (try $ setOpt access3 (123 % 100))
        (try @SomeException $ setOpt access3 (123001 % 100000)) >>= \case
          Right [] -> return ()
          Right w -> assertFailure $ "Unexpected warnings: " <> show w
          Left e -> assertFailure $ "Unexpected exception: " <> displayException e
  
    , testCaseSteps "get and set option values by name" $ \step ->
        withChecklist "multiple values" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "main.optstr"
            o2 = configOption BaseIntegerRepr "main.set.cfg.optint"
            o3 = configOption BaseBoolRepr "main.set.cfg.optbool"
            o4 = configOption BaseIntegerRepr "alt.optint"
            o1' = mkOpt o1 stringOptSty Nothing (Just $ ConcreteString "strval")
            o2' = mkOpt o2 integerOptSty Nothing (Just $ ConcreteInteger 11)
            o3' = mkOpt o3 boolOptSty Nothing (Just $ ConcreteBool True)
            o4' = mkOpt o4 integerOptSty Nothing (Just $ ConcreteInteger 88)
        extendConfig [o4', o3', o2', o1'] cfg
        accessSome1 <- getOptionSettingFromText "main.optstr" cfg
        accessSome2 <- getOptionSettingFromText "main.set.cfg.optint" cfg
        accessSome3 <- getOptionSettingFromText "main.set.cfg.optbool" cfg
        accessSome4 <- getOptionSettingFromText "alt.optint" cfg
  
        access1 <- getOptionSetting o1 cfg
        access2 <- getOptionSetting o2 cfg
        access3 <- getOptionSetting o3 cfg
        access4 <- getOptionSetting o4 cfg
  
        step "getting with a Some OptionSetter requires type verification"
        let cmpUnderSome :: Some OptionSetting -> T.Text -> IO ()
            cmpUnderSome (Some getter) v =
              case testEquality
                   (configOptionType (optionSettingName getter))
                   (BaseStringRepr UnicodeRepr) of
                Just Refl -> do vt <- getMaybeOpt getter
                                Just v @=? vt
                Nothing -> assertFailure "invalid option type"
        cmpUnderSome accessSome1 "strval"
  
        step "setting using special setting functions"
        let goodNoWarn f s v =
              (try @SomeException $ f s v) >>= \case
              Right [] -> return ()
              Right w -> assertFailure $ "Unexpected warnings: " <> show w
              Left e -> assertFailure $ "Unexpected exception: " <> displayException e
        goodNoWarn setUnicodeOpt accessSome1 "wild carrots"
        goodNoWarn setIntegerOpt accessSome2 31
        goodNoWarn setIntegerOpt accessSome4 42
        goodNoWarn setBoolOpt accessSome3 False
  
        step "verify set values"
        (Just "wild carrots" @=?) =<< getMaybeOpt access1
        (Just 31 @=?) =<< getMaybeOpt access2
        (Just False @=?) =<< getMaybeOpt access3
        (Just 42 @=?) =<< getMaybeOpt access4
  
        step "cannot set values with wrong types"
        -- Note that using an OptionSetting allows compile-time
        -- elimination, but using a (Some OptionSetting) requires
        -- run-time type witnessing and validation
        wantOptSetFailure "type is a BaseStringRepr"
          =<< (try $ setIntegerOpt accessSome1 54)
        wantOptSetFailure "but given an integer"
          =<< (try $ setIntegerOpt accessSome1 54)
  
        wantOptSetFailure "type is a BaseStringRepr"
          =<< (try $ setBoolOpt accessSome1 True)
        wantOptSetFailure "but given a boolean"
          =<< (try $ setBoolOpt accessSome1 True)
  
        wantOptSetFailure "type is a BaseIntegerRepr"
          =<< (try $ setUnicodeOpt accessSome2 "fresh tomatoes")
        wantOptSetFailure "but given a text string"
          =<< (try $ setUnicodeOpt accessSome2 "fresh tomatoes")
  
  
    , testCaseSteps "get multiple values at once" $ \step ->
        withChecklist "multiple values" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "main.optstr"
            o2 = configOption BaseIntegerRepr "main.set.cfg.optint"
            o3 = configOption BaseBoolRepr "main.set.cfg.optbool"
            o4 = configOption BaseIntegerRepr "alt.optint"
            o1' = mkOpt o1 stringOptSty Nothing (Just $ ConcreteString "strval")
            o2' = mkOpt o2 integerOptSty Nothing (Just $ ConcreteInteger 11)
            o3' = mkOpt o3 boolOptSty Nothing (Just $ ConcreteBool True)
            o4' = mkOpt o4 integerOptSty Nothing (Just $ ConcreteInteger 88)
        extendConfig [o4', o3', o2', o1'] cfg
        access1 <- getOptionSetting o1 cfg
        access3 <- getOptionSetting o3 cfg
        access4 <- getOptionSetting o4 cfg
  
        step "set string opt"
        res1 <- setOpt access1 "flibberty"
        show <$> res1 @?= []
  
        step "set bool opt"
        res2 <- setOpt access3 False
        show <$> res2 @?= []
  
        step "set alt int opt"
        res4 <- setOpt access4 789
        show <$> res4 @?= []
  
        step "get main config values"
        res <- getConfigValues "main.set" cfg
        let msg = show . PP.pretty <$> res
        msg `checkValues`
          (Empty
          :> Val "num values" length 2
          :> Val "bool" (any (L.isInfixOf "main.set.cfg.optbool = False")) True
          :> Val "int" (any (L.isInfixOf "main.set.cfg.optint = 11")) True
          )
  
        step "get all config values"
        resAll <- getConfigValues "" cfg
        let msgAll = show . PP.pretty <$> resAll
        msgAll `checkValues`
          (Empty
          :> Val "num values" length 5
          :> Val "bool" (any (L.isInfixOf "main.set.cfg.optbool = False")) True
          :> Val "int" (any (L.isInfixOf "main.set.cfg.optint = 11")) True
          :> Val "alt int" (any (L.isInfixOf "alt.optint = 789")) True
          :> Val "str" (any (L.isInfixOf "main.optstr = \"flibberty\"")) True
          :> Val "verbosity" (any (L.isInfixOf "verbosity = 0")) True
          )
  
        step "get specific config value"
        resOne <- getConfigValues "alt.optint" cfg
        let msgOne = show . PP.pretty <$> resOne
        msgOne `checkValues`
          (Empty
          :> Val "num values" length 1
          :> Val "alt int" (any (L.isInfixOf "alt.optint = 789")) True
          )
  
        step "get unknown config value"
        resNope <- getConfigValues "fargle.bargle" cfg
        let msgNope = show . PP.pretty <$> resNope
        msgNope `checkValues` (Empty :> Val "num values" length 0)
  
  
    ]
  
  testDeprecated :: [TestTree]
  testDeprecated =
    [
      testCase "deprecation removal (case #1)" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "hello"
            o1' = mkOpt o1 stringOptSty (Just "greeting") Nothing
        extendConfig [deprecatedOpt [] o1'] cfg
        setter <- getOptionSetting o1 cfg
        res <- try $ setOpt setter "eh?"
        case res of
          Right warns ->
            fmap show warns @?=
            [ "DEPRECATED CONFIG OPTION WILL BE IGNORED: hello (no longer valid)"
            ]
          Left (SomeException e) -> assertFailure $ show e
  
    , testCase "deprecation rename (case #2)" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "hi"
            o2 = configOption (BaseStringRepr UnicodeRepr) "hello"
            o1' = deprecatedOpt [o2'] $
                  mkOpt o1 stringOptSty (Just "greeting") Nothing
            o2' = mkOpt o2 stringOptSty (Just "greeting") Nothing
        extendConfig [o2', o1'] cfg
        setter <- getOptionSetting o1 cfg
        res <- try $ setOpt setter "eh?"
        case res of
          Right warns ->
            fmap show warns @?=
            [ "DEPRECATED CONFIG OPTION USED: hi (renamed to: hello)"
            ]
          Left (SomeException e) -> assertFailure $ show e
  
    , testCase "deprecation rename (case #2), wrong order" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "yo"
            o2 = configOption (BaseStringRepr UnicodeRepr) "hello"
            o1' = deprecatedOpt [o2'] $
                  mkOpt o1 stringOptSty (Just "greeting") Nothing
            o2' = mkOpt o2 stringOptSty (Just "greeting") Nothing
        res <- try $ extendConfig [o1', o2'] cfg
        wantOptCreateFailure
          "replacement options must be inserted into Config \
          \before this deprecated option"
          res
  
    , testCase "deprecation rename and re-typed (case #3)" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "optstr"
            o2 = configOption BaseIntegerRepr "optnum"
            o1' = deprecatedOpt [o2'] $
                  mkOpt o1 stringOptSty (Just "some opt") Nothing
            o2' = mkOpt o2 integerOptSty (Just "some other opt") Nothing
        extendConfig [o2', o1'] cfg
        setter <- getOptionSetting o1 cfg
        res <- try $ setOpt setter "eh?"
        case res of
          Right warns ->
            fmap show warns @?=
            [ "DEPRECATED CONFIG OPTION USED: optstr::BaseStringRepr UnicodeRepr (changed to: \"optnum\"::BaseIntegerRepr); this value may be ignored"
            ]
          Left (SomeException e) -> assertFailure $ show e
  
    , testCase "deprecation, multiple replacements (case #4)" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "url"
            o2 = configOption (BaseStringRepr UnicodeRepr) "hostname"
            o3 = configOption BaseIntegerRepr "port"
            o1' = deprecatedOpt [o2', o3'] $
                  mkOpt o1 stringOptSty (Just "some opt") Nothing
            o2' = mkOpt o2 stringOptSty (Just "some other opt") Nothing
            o3' = mkOpt o3 integerOptSty (Just "some other opt") Nothing
        extendConfig [o3', o2', o1'] cfg
        setter <- getOptionSetting o1 cfg
        res <- try $ setOpt setter "here?"
        case res of
          Right warns ->
            fmap show warns @?=
            [ "DEPRECATED CONFIG OPTION USED: url::BaseStringRepr UnicodeRepr (replaced by: \"hostname\"::BaseStringRepr UnicodeRepr, \"port\"::BaseIntegerRepr); this value may be ignored"
            ]
          Left (SomeException e) -> assertFailure $ show e
  
    , testCase "deprecation, multiple + removed/split (case #4,(#1,#3))" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "url"
            o2 = configOption (BaseStringRepr UnicodeRepr) "hostname"
            o3 = configOption BaseIntegerRepr "port"
            o4 = configOption (BaseStringRepr UnicodeRepr) "host"
            o5 = configOption (BaseStringRepr UnicodeRepr) "domain"
            o1' = deprecatedOpt [o2', o3'] $
                  mkOpt o1 stringOptSty (Just "some opt") Nothing
            o2' = deprecatedOpt [o4', o5'] $
                  mkOpt o2 stringOptSty (Just "some other opt") Nothing
            o3' = deprecatedOpt [] $
                  mkOpt o3 integerOptSty (Just "some other opt") Nothing
            o4' = mkOpt o4 stringOptSty (Nothing) Nothing
            o5' = mkOpt o5 stringOptSty (Just "some opt") (Just $ ConcreteString "cow.barn")
        extendConfig [ o4', o5', o2', o3', o1' ] cfg
        setter <- getOptionSetting o1 cfg
        res <- try $ setOpt setter "here?"
        case res of
          Right warns ->
            fmap show warns @?=
            [ "DEPRECATED CONFIG OPTION USED: url::BaseStringRepr UnicodeRepr (replaced by: \"host\"::BaseStringRepr UnicodeRepr, \"domain\"::BaseStringRepr UnicodeRepr); this value may be ignored"
            ]
          Left (SomeException e) -> assertFailure $ show e
  
    ]
  
  testHelp :: [TestTree]
  testHelp =
    [
      testCase "builtin-only config help" $
      withChecklist "builtins" $ do
        cfg <- initialConfig 0 []
        help <- configHelp "" cfg
        let nonEmptyOr :: (a -> b) -> b -> [a] -> b
            nonEmptyOr f = foldr (\h _ -> f h)
        help `checkValues`
          (Empty
          :> Val "num" length 1
          :> Val "verbosity" (nonEmptyOr (L.isInfixOf "verbosity =" . show) False) True
          )
  
  
    , testCaseSteps "three item (1 deprecated) config help" $ \step ->
        withChecklist "three items" $ do
        cfg <- initialConfig 0 []
        let o1 = configOption (BaseStringRepr UnicodeRepr) "optstr"
            o2 = configOption BaseIntegerRepr "optnum"
            o3 = configOption BaseIntegerRepr "foo.bar.baz.num"
            o1' = mkOpt o1 stringOptSty (Just "some opt") Nothing
            o2' = mkOpt o2 integerOptSty (Just "some other opt") Nothing
            o3' = mkOpt o3 integerOptSty (Just "foo stuff") Nothing
            helpIncludes txts = any (\h -> all (\t -> L.isInfixOf t (show h)) txts)
        extendConfig [o2', deprecatedOpt [o2'] o1', o3'] cfg
        setter2 <- getOptionSetting o2 cfg
        setRes <- setOpt setter2 13
        setRes `checkValues` (Empty :> Val "no warnings" null True)
  
        step "all help"
        help <- configHelp "" cfg
        help `checkValues`
          (Empty
          :> Val "num" length 4
          :> Val "verbosity" (helpIncludes ["verbosity ="]) True
          :> Val "option 1" (helpIncludes ["optstr"
                                          , "some opt"
                                          , "DEPRECATED!"
                                          , "Suggest"
                                          , "to \"optnum\""
                                          ]) True
          :> Val "option 2" (helpIncludes ["optnum", "= 13", "some other opt"]) True
          :> Val "option 3" (helpIncludes ["foo.bar.baz.num", "foo stuff"]) True
          )
  
        step "sub help"
        subHelp <- configHelp "foo.bar" cfg
        subHelp `checkValues`
          (Empty
          :> Val "num" length 1
          :> Val "option 3" (helpIncludes ["foo.bar.baz.num", "foo stuff"]) True
          )
  
        step "specific help"
        spec <- configHelp "optstr" cfg
        spec `checkValues`
          (Empty
          :> Val "num" length 1
          :> Val "spec name" (helpIncludes ["optstr"]) True
          :> Val "spec opt help" (helpIncludes ["some opt"]) True
          :> Val "spec opt help deprecated" (helpIncludes [ "DEPRECATED!"
                                                          , "Suggest"
                                                          , "to \"optnum\""
                                                          ]) True
          )
  
        step "specific sub help"
        subspec <- configHelp "foo.bar.baz.num" cfg
        subspec `checkValues`
          (Empty
          :> Val "num" length 1
          :> Val "option 3" (helpIncludes ["foo.bar.baz.num", "foo stuff"]) True
          )
  
    ]
  
  instance TestShow (PP.Doc Void) where testShow = show
  instance TestShow [PP.Doc Void] where testShow = testShowList
  instance TestShow [String] where testShow = testShowList
  
  wantOptCreateFailure :: Show a => String -> Either SomeException a -> IO ()
  wantOptCreateFailure withText res = case res of
    Right r ->
      assertFailure ("Expected '" <> withText <>
                     "' but completed successfully with: " <> show r)
    Left err ->
      case fromException err of
        Just (e :: OptCreateFailure) ->
          withText `L.isInfixOf` (show e) @?
          ("Expected '" <> withText <> "' exception error but got: " <>
           displayException e)
        _ -> assertFailure $
             "Expected OptCreateFailure exception but got: " <>
             displayException err
  
  wantOptSetFailure :: Show a => String -> Either SomeException a -> IO ()
  wantOptSetFailure withText res = case res of
    Right r ->
      assertFailure ("Expected '" <> withText <>
                     "' but completed successfully with: " <> show r)
    Left err ->
      case fromException err of
        Just (e :: OptSetFailure) ->
          withText `L.isInfixOf` (show e) @?
          ("Expected '" <> withText <> "' exception error but got: " <>
           displayException e)
        _ -> assertFailure $
             "Expected OptSetFailure exception but got: " <>
             displayException err
  
  wantOptGetFailure :: Show a => String -> Either SomeException a -> IO ()
  wantOptGetFailure withText res = case res of
    Right r ->
      assertFailure ("Expected '" <> withText <>
                     "' but completed successfully with: " <> show r)
    Left err ->
      case fromException err of
        Just (e :: OptGetFailure) ->
          withText `L.isInfixOf` (show e) @?
          ("Expected '" <> withText <> "' exception error but got: " <>
           displayException e)
        _ -> assertFailure $
             "Expected OptGetFailure exception but got: " <>
             displayException err
  
  
  main :: IO ()
  main = defaultMain $
         testGroup "ConfigTests"
         [ testGroup "Set and get" $ testSetAndGet
         , testGroup "Deprecated Configs" $ testDeprecated
         , testGroup "Config help" $ testHelp
         ]
  