packages feed

mangrove-cli-0.4.0.0: test/Arbitrary.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE DataKinds          #-}
{-# LANGUAGE DeriveGeneric      #-}
{-# LANGUAGE FlexibleInstances  #-}
{-# LANGUAGE OverloadedStrings  #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications   #-}

module Arbitrary
  ( ArgList(..)
  , Name(..)
  ) where

import           Data.Char
import           Data.Text                 (Text)
import qualified Data.Text                 as T
import           System.Random
import           Test.QuickCheck           hiding (Result (..))
import           Test.QuickCheck.Gen       (Gen (..))
import           Test.QuickCheck.Instances ()

import           Mangrove
import           Mangrove.ParseTree
import           Mangrove.Render
import qualified Mangrove.Scheme.Sub       as Sub
import           Mangrove.Scheme.Unix
import qualified Mangrove.Scheme.Unix      as Unix
import           Mangrove.Stream
import           Mangrove.TextParser
import           Mangrove.Unix

--------------------------------------------------------------------------------
-- Arbitrary Name Generator

randomNameChar :: RandomGen g => g -> (Char, g)
randomNameChar gen = (chr (n + offset), gen')
  where
    (n, gen') = uniformR (0, 62) gen
    offset
      | n >= 0  && n < 10 = 48
      | n >= 10 && n < 36 = 55
      | n >= 36 && n < 62 = 61
      -- The only remaining case is n == 62.
      | otherwise = 33

randomNameText :: RandomGen g => g -> Int -> Text
randomNameText gen n = T.unfoldrN n (Just . randomNameChar) gen

genNameChar :: Gen Char
genNameChar = MkGen $ const . fst . randomNameChar

genNameText :: Gen Text
genNameText = MkGen randomNameText `suchThat` (not . T.null)

-- | newtype wrapper for 'Text' that holds results from 'genNameText'
newtype Name = Name { getName :: Text }
  deriving (Eq, Show)

getNames :: Functor f => f Name -> f Text
getNames = fmap getName

instance Arbitrary Name where
  arbitrary = Name <$> genNameText

--------------------------------------------------------------------------------
-- Generic ParseTrees

genParser :: Scheme s => Gen (s Int) -> Gen (ParseTree s Int)
genParser genScheme = sized $ \n -> oneof $
  if n <= 0
  then [ pure EmptyNode, ValueNode <$> arbitrary ]
  else [ pure EmptyNode
       , ValueNode <$> arbitrary
       , ParseNode <$> genScheme
       , ProdNode <$> arbitrary @(Int -> Int -> Int)
                  <*> genParser genScheme
                  <*> genParser genScheme
       , SumNode <$> genParser genScheme <*> genParser genScheme
         -- ManyNode can only give us a `UnixParser [Int]`, so we have
         -- to wrap it in order to make the types match.
       , (fmap . fmap) sum $ ManyNode <$> arbitrary <*> genParser genScheme
       ]

--------------------------------------------------------------------------------
-- SubScheme Parsers

instance Arbitrary (Token SubScheme) where
  arbitrary =
    oneof [ SubAssoc <$> genNameText <*> arbitrary
          , SubArgument <$> arbitrary
          ]

instance CoArbitrary (Token SubScheme)

genSubScheme :: Gen (SubScheme Int)
genSubScheme =
  oneof [ pure $ Sub.Parameter defaultParser
        , flip Sub.Option defaultParser <$> arbitrary
        ]

instance Arbitrary (ParseTree SubScheme Int) where
  arbitrary = genParser genSubScheme

--------------------------------------------------------------------------------
-- UnixScheme Parsers

instance Arbitrary Flag where
  arbitrary =
    oneof [ LongFlag <$> genNameText
          , ShortFlag <$> genNameChar
          ]

instance CoArbitrary Flag

instance Arbitrary Unix.OptionInfo where
  arbitrary = OptionInfo <$> arbitrary <*> arbitrary

instance Arbitrary Unix.CommandInfo where
  arbitrary = CommandInfo <$> fmap getNames arbitrary <*> arbitrary

instance Arbitrary (Token UnixScheme) where
  arbitrary =
    oneof [ UnixArgument <$> arbitrary
          , UnixCommand <$> genNameText
          , UnixOption <$> arbitrary <*> arbitrary
          ]

instance CoArbitrary (Token UnixScheme)

instance Arbitrary UnixRequest where
  arbitrary = elements [HelpRequest [], VersionRequest]

instance CoArbitrary UnixRequest

genUnixScheme :: Gen (UnixScheme Int)
genUnixScheme =
  oneof [ pure $ Unix.Parameter defaultParser
        , Unix.Option <$> arbitrary <*> arbitrary
        , Unix.Command <$> arbitrary <*> arbitrary
        , Unix.RequestOption <$> arbitrary <*> arbitrary
        ]

instance Arbitrary (ParseTree UnixScheme Int) where
  arbitrary = genParser genUnixScheme

--------------------------------------------------------------------------------
-- StreamParsers

genUnixArgument :: Gen Text
genUnixArgument = renderText <$> arbitrary @(Token UnixScheme)

genUnixArgs :: Gen [Text]
genUnixArgs = sized $ \n -> vectorOf n genUnixArgument

newtype ArgList = ArgList { getArgs :: [Text] }
  deriving (Show)

instance Arbitrary ArgList where
  arbitrary = ArgList <$> genUnixArgs

instance Arbitrary (StreamState (Token UnixScheme)) where
  arbitrary = StreamState <$> genUnixArgs <*> arbitrary <*> arbitrary

instance CoArbitrary (StreamState (Token UnixScheme))