packages feed

opt-env-conf-test-0.0.0.2: test/OptEnvConf/ArgsSpec.hs

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}

module OptEnvConf.ArgsSpec (spec) where

import Data.List.NonEmpty (NonEmpty (..))
import OptEnvConf.Args
import OptEnvConf.Args.Gen ()
import Test.QuickCheck hiding (Args)
import Test.Syd
import Test.Syd.Validity

spec :: Spec
spec = do
  describe "parseArg" $ do
    it "produces valid args" $
      producesValid parseArg
    it "roundtrips with renderArg" $
      forAllValid $ \s ->
        renderArg (parseArg s) `shouldBe` s

  describe "parseArgs" $ do
    it "produces valid Args" $
      producesValid parseArgs

    let annoyingStrings :: Gen [String]
        annoyingStrings = genListOf $ genListOf $ oneof [genValid, pure '-']
    it "produces valid Argss for annoying strings" $
      forAll annoyingStrings $
        shouldBeValid . parseArgs

    it "parses empty args as an empty arg map" $
      parseArgs [] `shouldBe` emptyArgs

  describe "renderDashed" $ do
    it "roundtrips with parseArg for long dashed" $
      forAllValid $ \n ->
        parseArg (renderDashed (DashedLong n)) `shouldBe` ArgDashed True n

    it "roundtrips with parseArg for short dashed" $
      forAllValid $ \c ->
        parseArg (renderDashed (DashedShort c)) `shouldBe` ArgDashed False (c :| [])

  describe "consumeArgument" $ do
    it "does not consume anything if there is nothing to consume" $
      consumeArgument [] `shouldBe` [(Nothing, emptyArgs)]
    it "consumes a plain argument when there is one" $
      forAllValid $ \s ->
        let as = [Live (ArgPlain s)]
         in consumeArgument as
              `shouldBe` [(Just s, Args [Dead] []), (Nothing, as)]
    it "consumes a bare double-dash if it's the last argument" $
      forAllValid $ \befores ->
        let as = Args befores [Live ArgBareDoubleDash]
         in consumeArgument as
              `shouldBe` [ (Just "--", Args (befores ++ [Dead]) []),
                           (Nothing, as)
                         ]
    it "consumes any argument after a double-dash as an argument" $
      forAllValid $ \befores ->
        forAllValid $ \bareArg ->
          forAllValid $ \rest ->
            let as = Args befores (Live ArgBareDoubleDash : Live bareArg : rest)
             in consumeArgument as
                  `shouldBe` [ (Just (renderArg bareArg), Args befores (Live ArgBareDoubleDash : Dead : rest)),
                               (Nothing, as)
                             ]
    it "skips dead arguments" $
      forAllValid $ \befores ->
        forAllValid $ \afters ->
          consumeArgument (Args befores (Dead : afters)) `shouldBe` consumeArgument (Args (befores ++ [Dead]) afters)

    it "tries to consume dashed argument followed by a dead argument" $
      forAllValid $ \befores ->
        forAllValid $ \isLong ->
          forAllValid $ \cs ->
            let d = ArgDashed isLong cs
                args = Args befores [Live d, Dead]
             in consumeArgument args
                  `shouldBe` [ (Nothing, Args (befores ++ [Live d, Dead]) []),
                               (Just (renderArg d), Args (befores ++ [Dead]) [Dead])
                             ]

    it "tries to consume dashed argument followed by a live argument" $
      forAllValid $ \befores ->
        forAllValid $ \isLong ->
          forAllValid $ \cs ->
            forAll (genValid `suchThat` (/= ArgBareDoubleDash)) $ \arg ->
              let d = ArgDashed isLong cs
                  args = Args befores [Live d, Live arg]
               in context (ppShow args) $
                    consumeArgument args
                      `shouldBe` [ (Nothing, Args (befores ++ [Live d, Live arg]) []),
                                   -- Consuming the value (dashed is a switch) is
                                   -- more likely than consuming the dashed as an
                                   -- argument
                                   (Just (renderArg arg), Args (befores ++ [Live d, Dead]) []),
                                   (Just (renderArg d), Args (befores ++ [Dead]) [Live arg])
                                 ]
    it "tries to ignore this value that looks like an option value" $
      consumeArgument ["-p1", "--port", "2"]
        `shouldBe` [ (Nothing, Args ["-p1", "--port", "2"] []),
                     (Just "2", Args ["-p1", "--port", Dead] []),
                     (Just "--port", Args ["-p1", Dead] ["2"]),
                     (Just "-p1", Args [Dead] ["--port", "2"])
                   ]

  describe "consumeSwitch" $ do
    it "fails to consume if there are no dasheds" $
      forAllValid $ \as ->
        consumeSwitch [] as `shouldBe` Nothing
    it "fails to consume if there are no arguments" $
      forAllValid $ \ds ->
        consumeSwitch ds [] `shouldBe` Nothing

    it "does not consume a mismatched switch" $
      consumeSwitch ["--foo"] ["--bar"] `shouldBe` Nothing
    it "consumes a short switch if there are no other args" $
      consumeSwitch ["-v"] ["-v"] `shouldBe` Just [Dead]
    it "consumes a long switch if there are no other args" $
      consumeSwitch ["--verbose"] ["--verbose"] `shouldBe` Just [Dead]
    it "consumes a switch at the front first" $
      consumeSwitch ["-a", "-b"] ["-a", "-b"] `shouldBe` Just [Dead, "-b"]
    it "consumes a folded switch at the front first" $
      consumeSwitch ["-a", "-b"] ["-ab"] `shouldBe` Just ["-b"]

  describe "consumeOption" $ do
    it "fails to consume if there are no dasheds" $
      forAllValid $ \as ->
        consumeOption [] as `shouldBe` Nothing
    it "fails to consume if there are no arguments" $
      forAllValid $ \ds ->
        consumeOption ds [] `shouldBe` Nothing
    it "does not consume a mismatched option" $
      consumeOption ["--foo"] ["--bar", "quux"] `shouldBe` Nothing
    it "consumes a short option" $
      consumeOption ["-f"] ["-f", "foo"] `shouldBe` Just ("foo", [Dead])
    it "consumes a long option" $
      consumeOption ["--foo"] ["--foo", "foo"] `shouldBe` Just ("foo", [Dead])
    it "consumes an option at the front first" $
      consumeOption ["-f"] ["-f", "foo", "-f", "bar"] `shouldBe` Just ("foo", [Dead, "-f", "bar"])
    it "consumes a folded option" $
      consumeOption ["-f"] ["-vf", "foo"] `shouldBe` Just ("foo", ["-v", Dead])
    it "consumes a long option with an equals sign" $
      consumeOption ["--file"] ["--file=foo.txt"] `shouldBe` Just ("foo.txt", [Dead])
    it "consumes a short option in shorthand notation" $
      consumeOption ["-f"] ["-ffoo.txt"] `shouldBe` Just ("foo.txt", [Dead])
    it "consumes a short option before a long option" $
      consumeOption
        ["-p", "--port"]
        ["-p1", "--port", "2"]
        `shouldBe` Just ("1", [Dead, "--port", "2"])
    it "consumes a short option before a long option" $
      consumeOption
        ["-p", "--port"]
        [Dead, "--port", "2"]
        `shouldBe` Just ("2", [Dead, Dead])