skeletest-0.4.0: test/Skeletest/Internal/CLISpec.hs
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
module Skeletest.Internal.CLISpec (spec) where
import Control.Monad ((>=>))
import Data.Dynamic (fromDynamic)
import Data.Map qualified as Map
import Data.Typeable (typeOf)
import Skeletest
import Skeletest.Internal.CLI
import Skeletest.Predicate qualified as P
import Skeletest.TestUtils.Integration
spec :: Spec
spec = do
spec_parseCliArgsWith
spec_getFlag
newtype MyFlag = MyFlag String
deriving (Eq)
newtype MyFlag2 = MyFlag2 String
deriving (Eq)
spec_parseCliArgsWith :: Spec
spec_parseCliArgsWith = do
describe "parseCliArgsWith" $ do
longFlagSpec
shortFlagSpec
optFlagSpec
reqFlagSpec
switchFlagSpec
multiFlagSpec
where
mkFlagInfos name mShort fspec = [(name, mShort, SomeFlagSpec fspec)]
longFlagSpec = do
let flags =
mkFlagInfos "foo" Nothing $
RequiredFlag
{ parse = pure . MyFlag
}
describe "long flag" $ do
it "parses" $ do
parseCliArgsWith flags ["--foo", "1"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "1")}
it "parses with equal sign" $ do
parseCliArgsWith flags ["--foo=1"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "1")}
it "parses argument containing equal sign" $ do
parseCliArgsWith flags ["--foo=1=2"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "1=2")}
it "errors if unknown" $ do
parseCliArgsWith [] ["--foo"]
`shouldSatisfy` parseFailure "Unknown flag: --foo"
shortFlagSpec = do
let flags =
mkFlagInfos "foo" (Just 'f') $
RequiredFlag
{ parse = pure . MyFlag
}
describe "short flag" $ do
it "parses" $ do
parseCliArgsWith flags ["-f", "123"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "123")}
it "parses multiple switches at once" $ do
let flags' =
[ ("flag-a", Just 'a', SomeFlagSpec $ SwitchFlag (MyFlag . show))
, ("flag-b", Just 'b', SomeFlagSpec $ SwitchFlag (MyFlag2 . show))
]
expected =
P.and
[ containsFlag (MyFlag "True")
, containsFlag (MyFlag2 "True")
]
parseCliArgsWith flags' ["-ab"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = expected}
it "parses short flag with arg without space" $ do
let flags' =
mkFlagInfos "foo" (Just 'f') $
RequiredFlag
{ parse = pure . MyFlag
}
parseCliArgsWith flags' ["-fasdf"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "asdf")}
it "errors if unknown" $ do
parseCliArgsWith [] ["-x"]
`shouldSatisfy` parseFailure "Unknown flag: -x"
optFlagSpec = do
let flags =
mkFlagInfos "foo" Nothing $
OptionalFlag
{ default_ = MyFlag ""
, parse = pure . MyFlag
}
describe "OptionalFlag" $ do
it "returns last flag" $ do
parseCliArgsWith flags ["--foo", "1", "--foo", "2"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "2")}
it "returns default if not set" $ do
parseCliArgsWith flags []
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "")}
it "errors if no argument" $ do
parseCliArgsWith flags ["--foo"]
`shouldSatisfy` parseFailure "Flag '--foo' requires argument"
reqFlagSpec = do
let flags =
mkFlagInfos "foo" Nothing $
RequiredFlag
{ parse = pure . MyFlag
}
describe "RequiredFlag" $ do
it "returns last flag" $ do
parseCliArgsWith flags ["--foo", "1", "--foo", "2"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "2")}
it "errors if not set" $ do
parseCliArgsWith flags []
`shouldSatisfy` parseFailure "Flag '--foo' is required"
it "errors if no argument" $ do
parseCliArgsWith flags ["--foo"]
`shouldSatisfy` parseFailure "Flag '--foo' requires argument"
switchFlagSpec = do
let flags =
mkFlagInfos "foo" Nothing $
SwitchFlag
{ fromBool = MyFlag . show
}
describe "SwitchFlag" $ do
it "returns True if set" $ do
parseCliArgsWith flags ["--foo"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "True")}
it "returns True if set any number of times" $ do
parseCliArgsWith flags ["--foo", "--foo", "--foo"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "True")}
it "returns False if not set" $ do
parseCliArgsWith flags []
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "False")}
it "errors if argument is set" $ do
parseCliArgsWith flags ["--foo=asdf"]
`shouldSatisfy` parseFailure "Flag '--foo' does not take arguments, got: asdf"
multiFlagSpec = do
let mkMultiFlags :: (Show a) => FlagType a -> FlagInfos
mkMultiFlags type_ =
mkFlagInfos "foo" (Just 'f') $
MultiFlag
{ type_
, parseMulti = pure . MyFlag . show
}
describe "MultiFlag" $ do
let flags = mkMultiFlags FlagType_Arg
it "parses none" $ do
parseCliArgsWith flags []
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "[]")}
it "parses one" $ do
parseCliArgsWith flags ["--foo", "1"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "[\"1\"]")}
it "parses multiple" $ do
parseCliArgsWith flags ["--foo", "1", "--foo", "2"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "[\"1\",\"2\"]")}
describe "FlagType_Switch" $ do
let switchFlags = mkMultiFlags FlagType_Switch
it "parses" $ do
parseCliArgsWith switchFlags ["--foo", "--foo"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "[True,True]")}
it "parses multiple short flags" $ do
parseCliArgsWith switchFlags ["-fff"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "[True,True,True]")}
it "errors if argument provided" $ do
parseCliArgsWith switchFlags ["--foo=asdf"]
`shouldSatisfy` parseFailure "Flag '--foo' does not take arguments, got: asdf"
describe "FlagType_Arg" $ do
let argFlags = mkMultiFlags FlagType_Arg
it "parses" $ do
parseCliArgsWith argFlags ["--foo", "1", "--foo", "2"]
`shouldSatisfy` P.con CLIParseSuccess{flagStore = containsFlag (MyFlag "[\"1\",\"2\"]")}
it "errors if no argument" $ do
parseCliArgsWith argFlags ["--foo"]
`shouldSatisfy` parseFailure "Flag '--foo' requires argument"
containsFlag f = (Map.lookup (typeOf f) >=> fromDynamic) P.>>> P.just (P.eq f)
parseFailure msg = P.con (CLIParseFailure (P.eq msg))
spec_getFlag :: Spec
spec_getFlag = do
describe "getFlag" $ do
integration . it "reads registered flag" $ do
runner <- getFixture @TestRunner
runner.setMainFile
[ "import Skeletest.Main"
, "import ExampleSpec (MyFlag)"
, "cliFlags = [flag @MyFlag]"
]
runner.addTestFile "ExampleSpec.hs" $
[ "module ExampleSpec (MyFlag, spec) where"
, "import Skeletest"
, ""
, "newtype MyFlag = MyFlag String"
, "instance IsFlag MyFlag where"
, " flagName = \"my-flag\""
, " flagHelp = \"example\""
, " flagSpec = RequiredFlag (Right . MyFlag)"
, ""
, "spec = it \"should get flag\" $ do"
, " MyFlag s <- getFlag"
, " s `shouldBe` \"hello world\""
]
_ <- expectSuccess $ runner.runTestsWith def{cliArgs = ["--my-flag", "hello world"]}
pure ()
integration . it "errors if flag is not registered" $ do
runner <- getFixture @TestRunner
runner.addTestFile "ExampleSpec.hs" $
[ "module ExampleSpec (spec) where"
, ""
, "import Skeletest"
, ""
, "newtype MyFlag = MyFlag Bool"
, "instance IsFlag MyFlag where"
, " flagName = \"my-flag\""
, " flagHelp = \"example\""
, " flagSpec = SwitchFlag MyFlag"
, ""
, "spec = it \"should error\" $ do"
, " MyFlag _ <- getFlag"
, " pure ()"
]
(stdout, stderr) <- expectFailure runner.runTests
stderr `shouldBe` ""
stdout `shouldSatisfy` P.matchesSnapshot