elocrypt-2.1.0: test/Test/Elocrypt/QuickCheck.hs
-- |A re-export of QuickCheck along with some useful instances
module Test.Elocrypt.QuickCheck (
module Test.QuickCheck,
CliOptions(..),
LessThan20(..),
LessThan40(..),
LessThan79(..),
PhraseCliOptions(..),
WordCliOptions(..),
getOptions,
readHandle,
run
) where
import Data.Bool
import Data.List
import Data.Maybe
import System.Random
import Test.QuickCheck
import Test.Proctest hiding (run)
import Test.Proctest.Assertions
import qualified Test.Proctest as Proctest
import Data.Elocrypt
instance Arbitrary GenOptions where
arbitrary = do
caps <- arbitrary
return genOptions{genCapitals=caps}
-- |A representation of elocrypt's command line
-- options
data CliOptions = CliOptions {
cliCapitals :: Bool,
cliDigits :: Bool,
cliLength :: Maybe Int,
cliMaxLength :: Maybe Int,
cliNumber :: Maybe Int,
cliPassphrase :: Bool,
cliSpecials :: Bool
} deriving Eq
instance Show CliOptions where
show = unwords . getOptions
-- |CliOptions for generating only passwords
newtype WordCliOptions
= WordCliOptions { getWordOpts :: CliOptions }
deriving Eq
instance Show WordCliOptions where
show = show . getWordOpts
instance Arbitrary WordCliOptions where
arbitrary = do
caps <- arbitrary
digits <- arbitrary
len <- arbitrary
num <- arbitrary
specials <- arbitrary
return $ WordCliOptions CliOptions{
cliCapitals = caps,
cliDigits = digits,
cliLength = fromPositive len,
cliMaxLength = Nothing,
cliNumber = fromPositive num,
cliPassphrase = False,
cliSpecials = specials
}
where fromPositive = fmap getPositive
-- |CliOptions for generating only passphrases
newtype PhraseCliOptions
= PhraseCliOptions { getPhraseOpts :: CliOptions }
deriving Eq
instance Show PhraseCliOptions where
show = show . getPhraseOpts
instance Arbitrary PhraseCliOptions where
arbitrary = do
caps <- arbitrary
digits <- arbitrary
minLen <- arbitrary
maxLen <- arbitrary
num <- arbitrary
specials <- arbitrary
-- CLI gets very inconsistent when len >= 80
-- TODO: Fix ^^^
let minLen' = fromLT40 minLen
maxLen' = (+) <$> minLen' <*> fromLT40 maxLen
return $ PhraseCliOptions CliOptions{
cliCapitals = caps,
cliDigits = digits,
cliLength = minLen',
cliMaxLength = maxLen',
-- CLI gets very inconsistent when number >= 20
-- TODO: Fix ^^^
cliNumber = fromLT20 num,
cliPassphrase = True,
cliSpecials = specials
}
newtype LessThan20 n = LT20 { getLT20 :: n } deriving (Eq, Show)
newtype LessThan40 n = LT40 { getLT40 :: n } deriving (Eq, Show)
newtype LessThan79 n = LT79 { getLT79 :: n } deriving (Eq, Show)
instance (Arbitrary n, Num n, Random n) => Arbitrary (LessThan20 n) where
arbitrary = LT20 <$> choose (1, 20)
instance (Arbitrary n, Num n, Random n) => Arbitrary (LessThan40 n) where
arbitrary = LT40 <$> choose (1, 40)
instance (Arbitrary n, Num n, Random n) => Arbitrary (LessThan79 n) where
arbitrary = LT79 <$> choose (1, 79)
-- |Convert CliOptions into a list of cli arguments
getOptions :: CliOptions -> [String]
getOptions opts
= caps ++ digits ++ specials ++ num ++ phrase ++ len ++ maxLen
where caps = ["-c" | cliCapitals opts]
digits = ["-d" | cliDigits opts]
len = maybe [] (singleton . show) (cliLength opts)
maxLen = maybe [] (singleton . show) (cliMaxLength opts)
maxLen' = cliLength opts >> cliMaxLength opts
num = maybe [] (("-n":) . singleton . show) (cliNumber opts)
phrase = ["-p" | cliPassphrase opts]
specials = ["-s" | cliSpecials opts]
-- |Run elocrypt with the given options
run :: CliOptions -> IO (Handle, Handle, Handle, ProcessHandle)
run opts = do
res@(_, _, _, p) <- Proctest.run "elocrypt" (getOptions opts)
sleep'
_ <- assertExitedSuccess (seconds 2) p
return res
-- Utilities
singleton :: a -> [a]
singleton a = [a]
fromLT20 :: Functor f => f (LessThan20 a) -> f a
fromLT40 :: Functor f => f (LessThan40 a) -> f a
fromLT79 :: Functor f => f (LessThan79 a) -> f a
fromPositive :: Functor f => f (Positive a) -> f a
fromLT20 = fmap getLT20
fromLT40 = fmap getLT40
fromLT79 = fmap getLT79
fromPositive = fmap getPositive
readHandle :: Handle -> IO String
readHandle = (<$>) asUtf8Str . waitOutput (seconds 5) 5000
assertExitedSuccess :: Timeout -> ProcessHandle -> IO Bool
assertExitedSuccess t = fmap (== ExitSuccess) . assertExitedTimeout t
assertExitedFailure :: Timeout -> ProcessHandle -> IO Bool
assertExitedFailure t = fmap not . assertExitedSuccess t
sleep' :: IO ()
sleep' = sleep (seconds 0.0001)