elocrypt 1.0.0 → 2.0.0
raw patch · 13 files changed
+642/−319 lines, 13 filesdep +hlintPVP ok
version bump matches the API change (PVP)
Dependencies added: hlint
API changes (from Hackage documentation)
- Data.Elocrypt: alphabet :: [Char]
+ Data.Elocrypt: GenOptions :: Bool -> GenOptions
+ Data.Elocrypt: [genCapitals] :: GenOptions -> Bool
+ Data.Elocrypt: capitalizeR :: MonadRandom m => Bool -> Char -> m Char
+ Data.Elocrypt: genOptions :: GenOptions
+ Data.Elocrypt: instance GHC.Classes.Eq Data.Elocrypt.GenOptions
+ Data.Elocrypt: instance GHC.Show.Show Data.Elocrypt.GenOptions
+ Data.Elocrypt: newtype GenOptions
+ Data.Elocrypt: nextLetter :: MonadRandom m => String -> m Char
+ Data.Elocrypt.Trigraph: defaultFrequencies :: [Rational] -> [Rational]
+ Data.Elocrypt.Trigraph: findWeights :: String -> Maybe [(Char, Rational)]
- Data.Elocrypt: first2 :: MonadRandom m => m String
+ Data.Elocrypt: first2 :: MonadRandom m => GenOptions -> m String
- Data.Elocrypt: genPassphrase :: RandomGen g => Int -> Int -> Int -> g -> ([String], g)
+ Data.Elocrypt: genPassphrase :: RandomGen g => Int -> Int -> Int -> GenOptions -> g -> ([String], g)
- Data.Elocrypt: genPassword :: RandomGen g => Int -> Bool -> g -> (String, g)
+ Data.Elocrypt: genPassword :: RandomGen g => Int -> GenOptions -> g -> (String, g)
- Data.Elocrypt: genPasswords :: RandomGen g => Int -> Int -> Bool -> g -> ([String], g)
+ Data.Elocrypt: genPasswords :: RandomGen g => Int -> Int -> GenOptions -> g -> ([String], g)
- Data.Elocrypt: lastN :: MonadRandom m => Int -> String -> m String
+ Data.Elocrypt: lastN :: MonadRandom m => GenOptions -> Int -> String -> m String
- Data.Elocrypt: mkPassphrase :: MonadRandom m => Int -> Int -> Int -> m [String]
+ Data.Elocrypt: mkPassphrase :: MonadRandom m => Int -> Int -> Int -> GenOptions -> m [String]
- Data.Elocrypt: mkPassword :: MonadRandom m => Int -> Bool -> m String
+ Data.Elocrypt: mkPassword :: MonadRandom m => Int -> GenOptions -> m String
- Data.Elocrypt: mkPasswords :: MonadRandom m => Int -> Int -> Bool -> m [String]
+ Data.Elocrypt: mkPasswords :: MonadRandom m => Int -> Int -> GenOptions -> m [String]
- Data.Elocrypt: newPassphrase :: RandomGen g => Int -> Int -> Int -> g -> [String]
+ Data.Elocrypt: newPassphrase :: RandomGen g => Int -> Int -> Int -> GenOptions -> g -> [String]
- Data.Elocrypt: newPassword :: RandomGen g => Int -> Bool -> g -> String
+ Data.Elocrypt: newPassword :: RandomGen g => Int -> GenOptions -> g -> String
- Data.Elocrypt: newPasswords :: RandomGen g => Int -> Int -> Bool -> g -> [String]
+ Data.Elocrypt: newPasswords :: RandomGen g => Int -> Int -> GenOptions -> g -> [String]
- Data.Elocrypt: next :: MonadRandom m => String -> m Char
+ Data.Elocrypt: next :: MonadRandom m => GenOptions -> String -> m Char
Files
- README.md +8/−3
- elocrypt.cabal +14/−2
- src/cli/Main.hs +19/−8
- src/lib/Data/Elocrypt.hs +93/−69
- src/lib/Data/Elocrypt/Trigraph.hs +13/−0
- test/HLint.hs +15/−0
- test/IntegTest/Elocrypt/PassphraseTest.hs +79/−72
- test/IntegTest/Elocrypt/PasswordTest.hs +59/−60
- test/IntegTests.hs +0/−1
- test/Test/Elocrypt/Instances.hs +20/−5
- test/Test/Elocrypt/QuickCheck.hs +154/−0
- test/Test/Elocrypt/TrigraphTest.hs +27/−8
- test/Test/ElocryptTest.hs +141/−91
README.md view
@@ -15,13 +15,18 @@ ``` Binaries are also available:- * [elocrypt-0.6.0-linux-bin.tar.gz](https://github.com/sgillespie/elocrypt/releases/download/v0.4.0/elocrypt-0.4.0-linux-bin.tar.gz)- * [elocrypt-0.6.0-windows-bin.exe](https://github.com/sgillespie/elocrypt/releases/download/v0.4.0/elocrypt-0.4.0-windows-bin.exe)+ * [elocrypt-2.0.0-linux-bin.tar.gz](https://github.com/sgillespie/elocrypt/releases/download/v1.0.0/elocrypt-1.0.0-linux-bin.tar.gz)+ * [elocrypt-2.0.0-windows-bin.exe](https://github.com/sgillespie/elocrypt/releases/download/v1.0.0/elocrypt-1.0.0-windows-bin.exe) ## Running-Running elocrypt is as simple as:+Generate a list passwords: ``` elocrypt [length]+```++You can also generate random phrases (1 per line):+```+elocrypt --passphrases [min-length] [max-length] ``` ## Obtaining the source
elocrypt.cabal view
@@ -1,5 +1,5 @@ name: elocrypt-version: 1.0.0+version: 2.0.0 synopsis: Generate easy-to-remember, hard-to-guess passwords homepage: https://www.github.com/sgillespie/elocrypt license: OtherLicense@@ -16,7 +16,7 @@ cabal-version: >=1.10 description:- Generates pronounceable, hard-to-guess passwords.. as hard as+ Generates pronounceable, hard-to-guess passwords--as hard as Vince Carter's knee cartilage is. source-repository head@@ -51,6 +51,7 @@ build-depends: base >= 4.7 && <5, elocrypt, MonadRandom,+ proctest, QuickCheck, random, tasty,@@ -60,6 +61,7 @@ hs-source-dirs: test main-is: Tests.hs other-modules: Test.Elocrypt.Instances,+ Test.Elocrypt.QuickCheck, Test.Elocrypt.TrigraphTest, Test.ElocryptTest type: exitcode-stdio-1.0@@ -79,5 +81,15 @@ main-is: IntegTests.hs other-modules: IntegTest.Elocrypt.PassphraseTest, IntegTest.Elocrypt.PasswordTest,+ Test.Elocrypt.QuickCheck, Test.Elocrypt.Instances type: exitcode-stdio-1.0++test-suite elocrypt-lint+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: HLint.hs+ build-depends: base >= 4.7 && < 5,+ hlint+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010
src/cli/Main.hs view
@@ -1,6 +1,7 @@ module Main where import Control.Monad+import Data.Char import Data.List import Data.Maybe (fromMaybe) import System.Console.GetOpt@@ -15,11 +16,12 @@ termLen :: Int termHeight :: Int -version = "elocrypt 1.0.0"+version = "elocrypt 2.0.0" termLen = 80 termHeight = 10 data Options = Options {+ optCapitals :: Bool, -- Include capital letters? optLength :: Int, -- Size of the password(s) optMaxLength :: Int, optNumber :: Maybe Int, -- Number of passwords to generate@@ -35,6 +37,7 @@ defaultOptions :: Options defaultOptions = Options {+ optCapitals = False, optLength = 8, optMaxLength = 10, optNumber = Nothing,@@ -45,6 +48,10 @@ options :: [OptDescr (Options -> Options)] options = [+ Option ['c'] ["capitals"] + (NoArg (\o -> o { optCapitals = True }))+ "Include capital letters",+ Option ['n'] ["number"] (ReqArg (\n o -> o { optNumber = Just (read n) }) "NUMBER") "The number of passwords to generate",@@ -87,8 +94,8 @@ return $ case nonopts of (o:os:_) -> opts { optLength = read o, optMaxLength = read os }- (o:_) -> opts { optLength = read o }- [] -> opts+ (o:_) -> opts { optLength = read o }+ [] -> opts elocryptOpts' :: [String] -> IO (Options, [String]) elocryptOpts' args = case getOpt Permute options args of@@ -102,26 +109,30 @@ exitFailure generate :: RandomGen g => Options -> g -> String-generate opts@Options{optPassType=Word} = passwords opts+generate opts@Options{optPassType=Word} = passwords opts generate opts@Options{optPassType=Phrase} = passphrases opts passwords :: RandomGen g => Options -> g -> String-passwords Options{optLength = len, optNumber = n} gen+passwords opts@Options{optLength = len, optNumber = n} gen = format " " . groupWith splitAt' width " " $ ps- where ps = newPasswords len num False gen -- TODO[sgillespie]: Add caps+ where ps = newPasswords len num (getGenOptions opts) gen cols = columns len num = fromMaybe (nWords cols) n width = max termLen (len + 2) passphrases :: RandomGen g => Options -> g -> String-passphrases Options{optLength = minLen,+passphrases opts@Options{optCapitals = caps,+ optLength = minLen, optMaxLength = maxLen, optNumber = n} gen = format " " . take lines' . groupWith splitAt' width " " $ passphrase- where passphrase = newPassphrase words' minLen maxLen gen+ where passphrase = newPassphrase words' minLen maxLen (getGenOptions opts) gen words' = columns minLen * lines' width = max termLen (maxLen + 1) lines' = fromMaybe termHeight n++getGenOptions :: Options -> GenOptions+getGenOptions Options{optCapitals=caps} = genOptions{genCapitals=caps} usage :: String usage = usageInfo (intercalate "\n" headerLines) options
src/lib/Data/Elocrypt.hs view
@@ -15,9 +15,26 @@ import Control.Monad import Control.Monad.Random hiding (next) import Data.Bool+import Data.Char import Data.Maybe import Prelude hiding (min, max) +-- * Data Types++-- |Options for generating passwords or passphrases. Do not use+-- this constructor directly. Instead use 'genOptions' to construct+-- an instance.+newtype GenOptions = GenOptions {+ genCapitals :: Bool+} deriving (Eq, Show)++-- |Default options for generating passwords or passphrases. This is+-- the preferred way to construct 'GenOptions'.+genOptions :: GenOptions+genOptions = GenOptions {+ genCapitals = False+}+ -- * Random password generators -- |Generate a password using the generator g, returning the result and the@@ -26,12 +43,12 @@ -- @ -- -- Generate a password of length 10 using the system generator -- myGenPassword :: IO (String, StdGen)--- myGenPassword = genPassword 10 True \`liftM\` getStdGen+-- myGenPassword = genPassword 10 genOptions \`liftM\` getStdGen -- @ genPassword :: RandomGen g- => Int -- ^ password length- -> Bool -- ^ include capitals?- -> g -- ^ random generator+ => Int -- ^ password length+ -> GenOptions -- ^ options+ -> g -- ^ random generator -> (String, g) genPassword len = runRand . mkPassword len @@ -41,13 +58,13 @@ -- @ -- -- Generate 10 passwords of length 10 using the system generator -- myGenPasswords :: IO ([String], StdGen)--- myGenPasswords = (\(ls, g) -> (ls, g) `liftM` genPasswords 10 10 True `liftM` getStdGen+-- myGenPasswords = (\(ls, g) -> (ls, g) `liftM` genPasswords 10 10 genOptions `liftM` getStdGen -- @ genPasswords :: RandomGen g- => Int -- ^ password length- -> Int -- ^ number of passwords- -> Bool -- ^ include capitals?- -> g -- ^ random generator+ => Int -- ^ password length+ -> Int -- ^ number of passwords+ -> GenOptions -- ^ options+ -> g -- ^ random generator -> ([String], g) genPasswords len n = runRand . mkPasswords len n @@ -56,12 +73,12 @@ -- @ -- -- Generate a password of length 10 using the system generator -- myNewPassword :: IO String--- myNewPassword = newPassword 10 True \`liftM\` getStdGen+-- myNewPassword = newPassword 10 genOptions \`liftM\` getStdGen -- @ newPassword :: RandomGen g- => Int -- ^ password length- -> Bool -- ^ include capitals?- -> g -- ^ random generator+ => Int -- ^ password length+ -> GenOptions -- ^ options+ -> g -- ^ random generator -> String newPassword len = evalRand . mkPassword len @@ -71,13 +88,13 @@ -- @ -- -- Generate 10 passwords of length 10 using the system generator -- myNewPasswords :: IO [String]--- myNewPasswords = genPasswords 10 10 True `liftM` getStdGen+-- myNewPasswords = genPasswords 10 10 genOptions `liftM` getStdGen -- @ newPasswords :: RandomGen g- => Int -- ^ password length- -> Int -- ^ number of passwords- -> Bool -- ^ include capitals?- -> g -- ^ random generator+ => Int -- ^ password length+ -> Int -- ^ number of passwords+ -> GenOptions -- ^ options+ -> g -- ^ random generator -> [String] newPasswords len n = evalRand . mkPasswords len n @@ -87,16 +104,16 @@ -- @ -- -- Generate a password of length 10 using the system generator -- myPassword :: IO String--- myPassword = evalRand (mkPassword 10 True) \`liftM\` getStdGen+-- myPassword = evalRand (mkPassword 10 genOptions) \`liftM\` getStdGen -- @ mkPassword :: MonadRandom m- => Int -- ^ password length- -> Bool -- ^ include capitals?+ => Int -- ^ password length+ -> GenOptions -- ^ options -> m String-mkPassword len _ = do- f2 <- reverse `liftM` first2+mkPassword len opts = do+ f2 <- reverse `liftM` first2 opts if len > 2- then reverse `liftM` lastN (len - 2) f2+ then reverse `liftM` lastN opts (len - 2) f2 else return . reverse . take len $ f2 -- |Plural version of mkPassword. Generate an infinite list of passwords using@@ -105,12 +122,12 @@ -- @ -- -- Generate an list of length 20 with passwords of length 10 using the system generator -- myMkPasswords :: IO [String]--- myMkPasswords = evalRand (mkPasswords 10 20 True) \`liftM\` getStdGen+-- myMkPasswords = evalRand (mkPasswords 10 20 genOptions) \`liftM\` getStdGen -- @ mkPasswords :: MonadRandom m- => Int -- ^ password length- -> Int -- ^ number of passwords- -> Bool -- ^ include capitals?+ => Int -- ^ password length+ -> Int -- ^ number of passwords+ -> GenOptions -- ^ options -> m [String] mkPasswords len n = replicateM n . mkPassword len @@ -123,15 +140,16 @@ -- -- Generate a passphrase of 10 words, each having a length between 6 and 12, -- -- using the system generator -- myGenPassphrase :: IO (String, StdGen)--- myGenPassphrase = genPassword 10 True \`liftM\` getStdGen+-- myGenPassphrase = genPassword 10 6 10 genOptions \`liftM\` getStdGen -- @ genPassphrase :: RandomGen g- => Int -- ^ number of words- -> Int -- ^ minimum word length- -> Int -- ^ maximum word length- -> g -- ^ random generator+ => Int -- ^ number of words+ -> Int -- ^ minimum word length+ -> Int -- ^ maximum word length+ -> GenOptions -- ^ options+ -> g -- ^ random generator -> ([String], g)-genPassphrase n min = runRand . mkPassphrase n min+genPassphrase n min max = runRand . mkPassphrase n min max -- |Generate a passphrase using the generator g, returning the result. --@@ -142,12 +160,13 @@ -- myNewPassphrase = newPassphrase 10 6 12 \`liftM\` getStdGen -- @ newPassphrase :: RandomGen g- => Int -- ^ number of words- -> Int -- ^ minimum word length- -> Int -- ^ maximum word length- -> g -- ^ random generator+ => Int -- ^ number of words+ -> Int -- ^ minimum word length+ -> Int -- ^ maximum word length+ -> GenOptions -- ^ options+ -> g -- ^ random generator -> [String]-newPassphrase n min = evalRand . mkPassphrase n min+newPassphrase n min max = evalRand . mkPassphrase n min max -- |Generate a finite number of words of random length (between @min@ and @max@ chars) -- using the MonadRandom m. MonadRandom is exposed here for extra control.@@ -158,45 +177,50 @@ -- myPassphrase = evalRand (mkPassphrase 10 6 12) \`liftM\` getStdGen -- @ mkPassphrase :: MonadRandom m- => Int -- ^ number of words- -> Int -- ^ minimum word length- -> Int -- ^ maximum word length+ => Int -- ^ number of words+ -> Int -- ^ minimum word length+ -> Int -- ^ maximum word length+ -> GenOptions -- ^ options -> m [String]-mkPassphrase n min max = replicateM n $- getRandomR (min, max) >>= flip mkPassword False+mkPassphrase n min max opts = replicateM n $+ getRandomR (min, max) >>= flip mkPassword opts -- * Internal --- |The alphabet we sample random values from-alphabet :: [Char]-alphabet = ['a'..'z']- -- |Generate two random characters. Uses 'Elocrypt.Trigraph.trigragh' -- to generate a weighted list.-first2 :: MonadRandom m => m String-first2 = fromList (map toWeight frequencies)+first2 :: MonadRandom m + => GenOptions+ -> m String+first2 opts = fromList (map toWeight frequencies) >>= mapM (capitalizeR caps) where toWeight (s, w) = (s, sum w)+ GenOptions{genCapitals=caps} = opts +-- |Generate the last n characters using previous two characters+-- and their 'Elocrypt.Trigraph.trigraph'+lastN :: MonadRandom m => GenOptions -> Int -> String -> m String+lastN _ 0 ls = return ls+lastN opts len ls = next opts ls >>= lastN opts (len - 1) . (:ls)+ -- |Generate a random character based on the previous two characters and -- their 'Elocrypt.Trigraph.trigraph'-next :: MonadRandom m => String -> m Char-next (x:xs:_) = fromList .- zip ['a'..'z'] .- defaultFreqs .- fromJust .- findFrequency $ [xs, x]-- -- Fix frequencies if they are all 0, since MonadRandom prohibits this.- -- Use all 1s in this case to give every item an equal weight.- where defaultFreqs :: [Rational] -> [Rational]- defaultFreqs f = bool (replicate 26 1) f (any (>0) f)---- This shouldn't ever happen-next _ = undefined+next :: MonadRandom m + => GenOptions -- ^ options+ -> String -- ^ the prefix+ -> m Char+next opts prefix = nextLetter prefix >>= capitalizeR caps+ where GenOptions{genCapitals=caps} = opts --- |Generate the last n characters using previous two characters--- and their 'Elocrypt.Trigraph.trigraph'-lastN :: MonadRandom m => Int -> String -> m String-lastN 0 ls = return ls-lastN len ls = next ls >>= lastN (len - 1) . (:ls)+-- |Randomly choose a letter from the trigraph+nextLetter :: MonadRandom m+ => String -- ^ the prefix+ -> m Char+nextLetter = fromList . fromJust . findWeights . reverse . take 2 +-- |Randomly capitalize a character 10% of the time+capitalizeR :: MonadRandom m+ => Bool -- ^ Whether to do the capitalization+ -> Char -- ^ The character to capitalize+ -> m Char+capitalizeR cap c | cap = fromList [(c, 6), (toUpper c, 1)]+ | otherwise = return c
src/lib/Data/Elocrypt/Trigraph.hs view
@@ -11,11 +11,24 @@ module Data.Elocrypt.Trigraph where import Control.Monad+import Data.Bool+import Data.Char import Data.List +-- |Search for the character frequencies and return a weighted list+findWeights :: String -- ^ The two letter prefix+ -> Maybe [(Char, Rational)]+findWeights = fmap (zip ['a'..'z'] . defaultFrequencies) . findFrequency'+ where findFrequency' = findFrequency . map toLower+ -- |Search for the character frequencies based on the first a two-letter string findFrequency :: String -> Maybe [Rational] findFrequency s = snd `liftM` find ((==) s . fst) frequencies++-- |Fix frequencies if they are all 0, since MonadRandom prohibits this.+-- In this case, use all 1s to give every item an equal weight+defaultFrequencies :: [Rational] -> [Rational]+defaultFrequencies f = bool (replicate 26 1) f (any (>0) f) -- | A map of character frequencies, based on a dictionary. The key is a two-letter -- string, and the value is a list of probabilities (a-z). It's form is:
+ test/HLint.hs view
@@ -0,0 +1,15 @@+module Main (main) where++import Language.Haskell.HLint (hlint)+import System.Exit (exitFailure, exitSuccess)++arguments :: [String]+arguments = [ + "src",+ "test"+ ]++main :: IO ()+main = hlint arguments >>= main'+ where main' [] = exitSuccess+main' _ = exitFailure
test/IntegTest/Elocrypt/PassphraseTest.hs view
@@ -2,113 +2,120 @@ module IntegTest.Elocrypt.PassphraseTest where import Control.Monad+import Data.Bool+import Data.Char import Data.List import Data.Maybe -import Test.Proctest-import Test.Proctest.Assertions import Test.QuickCheck import Test.Tasty hiding (Timeout) import Test.Tasty.QuickCheck (testProperty) import Test.Tasty.TH+import qualified Test.Proctest as Proctest -import Test.Elocrypt.Instances+import Test.Elocrypt.QuickCheck tests :: TestTree tests = $(testGroupGenerator) elocrypt = "elocrypt" -prop_printsWordsWithSpecifiedLengthRange :: PhraseCliArgs -> Property-prop_printsWordsWithSpecifiedLengthRange (PhraseCliArgs args)- = length (getPosParams args) == 2 ==>+-- |Passphrases consist of words in specified length range+prop_printsWordsWithLengthRange :: PhraseCliOptions -> Property+prop_printsWordsWithLengthRange (PhraseCliOptions opts)+ = isJust minLen && isJust maxLen && fromJust maxLen <= 79 ==> ioProperty $ do- (_, out', _, _) <- run' elocrypt args- response <- readHandle out'+ (_, out, _, _) <- run opts+ response <- readHandle out - let (minLen:maxLen:_) = map read (getPosParams args)- words' = words response+ let words' = words response+ minLen' = fromJust minLen+ maxLen' = fromJust maxLen - return (all ((\n -> n >= minLen && n <= maxLen) . length) words')+ return (all ((\n -> n >= minLen' && n <= maxLen') . length) words') -prop_printsWordsWithSpecifiedMinLengthRange :: PhraseCliArgs -> Property-prop_printsWordsWithSpecifiedMinLengthRange (PhraseCliArgs args)- = length (getPosParams args) == 1 ==>+ where CliOptions{cliLength=minLen, cliMaxLength=maxLen} = opts++-- |Passphrases consist of words with specified minimum range+prop_printsWordsWithMinLength:: PhraseCliOptions -> Property+prop_printsWordsWithMinLength(PhraseCliOptions opts)+ = isJust minLen && fromJust minLen <= 79 ==> ioProperty $ do- (_, out', _, _) <- run' elocrypt args- response <- readHandle out'+ (_, out, _, _) <- run opts+ response <- readHandle out - let (minLen:_) = map read (getPosParams args)+ return . all ((>= min (fromJust minLen) 10) . length) . words $ response - return . all ((>= min minLen 10) . length) . words $ response+ where CliOptions{cliLength=minLen} = opts -prop_printsWordsWithDefaultLengthRange :: PhraseCliArgs -> Property-prop_printsWordsWithDefaultLengthRange (PhraseCliArgs args)- = length (getPosParams args) == 0 ==>+-- |Prints the specificed number of phrases+prop_printsSpecifiedNumberOfPassphrases :: PhraseCliOptions -> Property+prop_printsSpecifiedNumberOfPassphrases (PhraseCliOptions opts)+ = isJust number && + (isJust maxLen || isNothing len) && -- maxLen > len+ maybe True (< 79) maxLen ==> -- minLen/maxLen < 79+ ioProperty $ do- (_, out', _, _) <- run' elocrypt args- response <- readHandle out' - let words' = words response+ (_, out, _, _) <- run opts+ response <- readHandle out - return (all ((\n -> n >= 8 && n <= 10) . length) words')+ let phrases = lines response -prop_printsSpecifiedNumberOfPassphrases :: PhraseCliArgs -> Property-prop_printsSpecifiedNumberOfPassphrases (PhraseCliArgs args)- = isJust (getArg "-n" args) &&- length (getPosParams args) /= 1 ==> -- Make sure maxLen > minLen- ioProperty $ do- (_, out', _, _) <- run' elocrypt args- response <- readHandle out'+ return $ counterexample (failMsg phrases) + (fromJust number == length phrases) - let number = read . fromJust . getArg "-n" $ args- phrases = lines response+ where CliOptions{cliNumber=number, cliLength=len, cliMaxLength=maxLen} = opts+ failMsg p = "length phrases (" ++ show (length p) ++ + ") /= " ++ show (fromJust number) - return (number == length phrases)+-- |Prints multiple passwords per line+prop_printsMultipleWordsPerLine :: PhraseCliOptions + -> LessThan20 Int+ -> LessThan20 Int+ -> Property+prop_printsMultipleWordsPerLine (PhraseCliOptions opts) (LT20 min) (LT20 max)+ = ioProperty $ do+ let opts' = opts { -- Make sure we can fit 2+ words on a line+ cliLength = Just min, + cliMaxLength = Just max} -prop_printsMultipleWordsPerLine :: PhraseCliArgs -> Property-prop_printsMultipleWordsPerLine (PhraseCliArgs args)- = sum (map read (getPosParams args) :: [Integer]) <= 29 ==>- ioProperty $ do- (_, out', _, _) <- run' elocrypt args- response <- readHandle out'+ (_, out, _, _) <- run opts'+ response <- readHandle out return $ all (>1) . tail . reverse . map (length . words) . lines $ response -prop_printsLongPassphrases :: GreaterThan79 Int -> GreaterThan0 Int -> Property-prop_printsLongPassphrases (GT79 minLen) (GT0 maxLen)- = ioProperty $ do- (_, out', _, _) <- run' elocrypt ["-p",- show minLen,- show (minLen + maxLen)]- response <- readHandle out'- - return . all (==1) . map (length . words) . lines $ response--getArg :: String -> [String] -> Maybe String-getArg prefix args = (tail . dropWhile (not . elem')) `liftM` arg- where arg = find (isPrefixOf prefix) args- elem' = (flip elem) [' ', '=']--getPosParams :: [String] -> [String]-getPosParams = filter ((/= '-') . head)+-- |Always prints a passphrase+prop_printsLongPassphrases :: PhraseCliOptions+ -> Positive Int+ -> Positive Int + -> Property+prop_printsLongPassphrases (PhraseCliOptions opts) (Positive min) (Positive max)+ = forAll (scale (*7) arbitrary) $ \min ->+ ioProperty $ do+ let minLen = getPositive min+ opts' = opts{+ cliLength = Just minLen,+ cliMaxLength = Just (minLen + max)} -run' :: FilePath -> [String] -> IO (Handle, Handle, Handle, ProcessHandle)-run' exe args = do- res@(_, _, _, p) <- run exe args- sleep'- _ <- assertExitedSuccess (seconds 2) p- return res+ (_, out, _, _) <- run opts+ response <- readHandle out+ + return $+ cover (minLen > 80) 20 "long" $ + all (>=1) . map (length . words) . lines $ response -readHandle :: Handle -> IO String-readHandle = (<$>) asUtf8Str . waitOutput (seconds 2) 5000+-- |Prints capitals when specified+prop_printsCapitals :: PhraseCliOptions -> Property+prop_printsCapitals (PhraseCliOptions opts)+ = ioProperty $ do+ let opts' = opts { cliCapitals = True } -sleep' :: IO ()-sleep' = sleep (seconds 0.0001)+ (_, out, _, _) <- run opts'+ response <- readHandle out -assertExitedSuccess :: Timeout -> ProcessHandle -> IO Bool-assertExitedSuccess t = liftM (== ExitSuccess) . assertExitedTimeout t+ let phrases = lines response -assertExitedFailure :: Timeout -> ProcessHandle -> IO Bool-assertExitedFailure t = liftM not . assertExitedSuccess t+ return $+ cover (any (any isUpper) phrases) 80 "has caps" True
test/IntegTest/Elocrypt/PasswordTest.hs view
@@ -1,99 +1,98 @@ {-# LANGUAGE TemplateHaskell #-} module IntegTest.Elocrypt.PasswordTest where +import Data.Char import Data.List import Data.Maybe import Control.Monad -import Test.Proctest-import Test.Proctest.Assertions-import Test.QuickCheck import Test.Tasty hiding (Timeout) import Test.Tasty.QuickCheck (testProperty) import Test.Tasty.TH -import Test.Elocrypt.Instances+import Test.Elocrypt.QuickCheck tests :: TestTree tests = $(testGroupGenerator) elocrypt = "elocrypt" -prop_printsPasswordsWithSpecifiedLength :: CliArgs -> Property-prop_printsPasswordsWithSpecifiedLength (CliArgs args)- = isJust (getPosParam args) ==>+-- |All passwords have specified length+prop_printsPasswordsWithLength :: WordCliOptions -> Property+prop_printsPasswordsWithLength (WordCliOptions opts)+ = isJust len ==> ioProperty $ do- (in', out', err', p) <- run' elocrypt args- response <- readHandle out'+ (_, out, _, _) <- run opts+ response <- readHandle out - let len = read . fromJust . getPosParam $ args+ let len' = fromJust len words' = words response - return (all ((==) len . length) words')+ return (all ((==) len' . length) words') -prop_printsNothingWhenSpecifiedLengthIsZero :: CliArgs -> Property-prop_printsNothingWhenSpecifiedLengthIsZero (CliArgs args)- = isNothing (getPosParam args) ==>- ioProperty $ do- let args' = args ++ ["0"]+ where CliOptions{cliLength=len} = opts - (in', out', err', p) <- run' elocrypt args'- response <- readHandle out'+-- |Prints nothing when length is 0+prop_printsNothingWhenLengthIsZero :: WordCliOptions -> Property+prop_printsNothingWhenLengthIsZero (WordCliOptions opts)+ = ioProperty $ do+ let opts' = opts { cliLength = Just 0 }++ (_, out, _, _) <- run opts'+ response <- readHandle out return (response == "") -prop_printsLongPasswords :: GreaterThan79 Int -> Property-prop_printsLongPasswords (GT79 a)+-- |Always prints at least 1 password+prop_printsLongPasswords :: WordCliOptions -> Property+prop_printsLongPasswords (WordCliOptions opts)+ = forAll (scale (*7) arbitrary) $ \len ->+ ioProperty $ do+ let len' = getPositive len+ opts' = opts { cliLength = Just len' }++ (_, out, _, _) <- run opts'+ response <- readHandle out++ return $+ cover (len' > 80) 30 "long" $+ all (>=1) . map (length . words) . lines $ response++-- |Prints the specified number of passwords+prop_printsNumberPasswords :: Positive Int -> WordCliOptions -> Property+prop_printsNumberPasswords (Positive num) (WordCliOptions opts) = ioProperty $ do- (in', out', err', p) <- run' elocrypt [show a]- response <- readHandle out'- return . all (==1) . map (length . words) . lines $ response+ let opts' = opts { cliNumber = Just num } -prop_printsSpecifiedNumberOfPasswords :: CliArgs -> Property-prop_printsSpecifiedNumberOfPasswords (CliArgs args)- = isJust (getArg "-n" args) ==>- ioProperty $ do- (in', out', err', p) <- run' elocrypt args- response <- readHandle out'+ (_, out, _, _) <- run opts'+ response <- readHandle out - let number = read . fromJust . getArg "-n" $ args- words' = words response+ let words' = words response - return (number == length words')+ return $ num == length words' -prop_printsMultiplePasswordsPerLine :: CliArgs -> Property-prop_printsMultiplePasswordsPerLine (CliArgs args)- = (read . fromMaybe "8" . getPosParam $ args) <= 38 ==>+-- |Prints multiple passwords per line when length is sufficiently small+prop_printsMultiplePasswordsPerLine :: WordCliOptions -> Property+prop_printsMultiplePasswordsPerLine (WordCliOptions opts)+ = isNothing len || fromJust len <= 38 ==> ioProperty $ do- (in', out', err', p) <- run' elocrypt args- response <- readHandle out'+ (_, out, _, _) <- run opts+ response <- readHandle out return $ all (>1) . tail . reverse . map (length . words) . lines $ response --- Utility functions-run' :: FilePath -> [String] -> IO (Handle, Handle, Handle, ProcessHandle)-run' exe args = do- res@(in', out', err', p) <- run exe args- sleep'- assertExitedSuccess (seconds 2) p- return res--readHandle :: Handle -> IO String-readHandle = (<$>) asUtf8Str . waitOutput (seconds 2) 5000--assertExitedSuccess :: Timeout -> ProcessHandle -> IO Bool-assertExitedSuccess t = liftM (== ExitSuccess) . assertExitedTimeout t+ where CliOptions{cliLength=len} = opts -assertExitedFailure :: Timeout -> ProcessHandle -> IO Bool-assertExitedFailure t = liftM not . assertExitedSuccess t+-- |Prints capitals when specified+prop_printsCapitals :: WordCliOptions -> Property+prop_printsCapitals (WordCliOptions opts)+ = ioProperty $ do+ let opts' = opts { cliCapitals = True } -sleep' :: IO ()-sleep' = sleep (seconds 0.0001)+ (_, out, _, _) <- run opts'+ response <- readHandle out -getArg :: String -> [String] -> Maybe String-getArg prefix args = (tail . dropWhile (not . elem')) `liftM` arg- where arg = find (isPrefixOf prefix) args- elem' = (flip elem) [' ', '=']+ let passes = words response -getPosParam :: [String] -> Maybe String-getPosParam = find $ (/= '-') . head+ return $+ cover (any (any isUpper) passes) 80 "has caps" True
test/IntegTests.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE TemplateHaskell #-} module Main where import Data.Functor
test/Test/Elocrypt/Instances.hs view
@@ -6,6 +6,8 @@ import Test.QuickCheck +-- |Representation of possible CLI arguments for a command line+-- app. newtype CliArgs = CliArgs { getArgs :: [String] } deriving Eq@@ -18,9 +20,12 @@ len <- arbitrary `suchThat` (>0) `suchThat` (<=40) :: Gen Int num <- arbitrary `suchThat` (>2) `suchThat` (<=20) :: Gen Int args <- sublistOf ["-n %d" `printf` num,+ "-c", show len] return (CliArgs args) +-- |Representation of an elocrypt execution with passphrases turned+-- on. newtype PhraseCliArgs = PhraseCliArgs { getPhraseArgs :: [String] } deriving Eq@@ -41,30 +46,40 @@ return $ PhraseCliArgs ("-p" : args) --- Uh oh! I copy/pasted this! instance Arbitrary StdGen where arbitrary = mkStdGen `liftM` arbitrary +-- |All letters, upper and lower case. newtype AlphaChar = Alpha Char deriving (Eq, Ord, Show)--- + instance Arbitrary AlphaChar where- arbitrary = Alpha `liftM` elements ['a'..'z']+ arbitrary = Alpha <$> elements (['a'..'z'] ++ ['A'..'Z']) +-- |All lowercase letters+newtype LowerAlphaChar = LowerAlpha Char+ deriving (Eq, Ord, Show)++instance Arbitrary LowerAlphaChar where+ arbitrary = LowerAlpha <$> elements ['a'..'z']++-- |DEPRECATED: Positive integers. newtype GreaterThan0 a = GT0 { getGT0 :: a } deriving (Eq, Ord, Show) instance (Num a, Ord a, Arbitrary a) => Arbitrary (GreaterThan0 a) where arbitrary = GT0 `fmap` (arbitrary `suchThat` (>0)) +-- |DEPRECATED: Integers greater than 2 newtype GreaterThan2 a = GT2 { getGT2 :: a } deriving (Eq, Ord, Show) instance (Num a, Ord a, Arbitrary a) => Arbitrary (GreaterThan2 a) where- arbitrary = GT2 `fmap` (arbitrary `suchThat` (>2))+ arbitrary = GT2 <$> (arbitrary `suchThat` (>2)) +-- |DEPRECATED: Integers greater than 79 newtype GreaterThan79 a = GT79 { getGT79 :: a } deriving (Eq, Ord, Show) instance (Integral a, Random a) => Arbitrary (GreaterThan79 a) where- arbitrary = GT79 `fmap` choose (80, 500)+ arbitrary = GT79 <$> choose (80, 500)
+ test/Test/Elocrypt/QuickCheck.hs view
@@ -0,0 +1,154 @@+-- |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,+ cliLength :: Maybe Int,+ cliMaxLength :: Maybe Int,+ cliNumber :: Maybe Int,+ cliPassphrase :: 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+ len <- arbitrary+ num <- arbitrary++ return $ WordCliOptions CliOptions{ + cliCapitals = caps,+ cliLength = fromPositive len,+ cliMaxLength = Nothing,+ cliNumber = fromPositive num,+ cliPassphrase = False+ }+ + 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+ minLen <- arbitrary+ maxLen <- arbitrary+ num <- arbitrary++ -- CLI gets very inconsistent when len >= 80+ -- TODO: Fix ^^^+ let minLen' = fromLT40 minLen+ maxLen' = (+) <$> minLen' <*> fromLT40 maxLen++ return $ PhraseCliOptions CliOptions{ + cliCapitals = caps,+ cliLength = minLen',+ cliMaxLength = maxLen', + -- CLI gets very inconsistent when number >= 20+ -- TODO: Fix ^^^+ cliNumber = fromLT20 num,+ cliPassphrase = True+ }++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 ++ num ++ phrase ++ len ++ maxLen+ where caps = ["-c" | cliCapitals 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]++-- |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 2) 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)
test/Test/Elocrypt/TrigraphTest.hs view
@@ -2,6 +2,8 @@ module Test.Elocrypt.TrigraphTest where import Control.Monad+import Data.Char+import Data.Maybe import Test.QuickCheck import Test.Tasty.QuickCheck (testProperty) import Test.Tasty.TH@@ -11,12 +13,29 @@ tests = $(testGroupGenerator) -prop_findFrequencyShouldSucceedWith2Chars :: AlphaChar -> AlphaChar -> Bool-prop_findFrequencyShouldSucceedWith2Chars (Alpha c1) (Alpha c2)- = fromJust (findFrequency [c1, c2])- where fromJust (Just _) = True- fromJust Nothing = False+-- |Finding a frequency with 2 lowercase letters returns a trigraph+prop_findFrequencyWith2Letters :: LowerAlphaChar -> LowerAlphaChar -> Bool+prop_findFrequencyWith2Letters (LowerAlpha c1) (LowerAlpha c2)+ = isJust (findFrequency [c1, c2]) -prop_findFrequencyShouldFailWithNot2Chars :: String -> Property-prop_findFrequencyShouldFailWithNot2Chars str- = length str /= 2 ==> findFrequency str == Nothing+-- |Finding a frequency with more or less than 2 characters results in nothing+prop_findFrequencyWithNot2Letters :: String -> Property+prop_findFrequencyWithNot2Letters str+ = not (length str == 2 && all isLower str) ==> isNothing (findFrequency str)++-- |Finding a weight with 2 letters returns a weighted list+prop_findWeightsWith2Letters :: AlphaChar -> AlphaChar -> Bool+prop_findWeightsWith2Letters (Alpha c1) (Alpha c2)+ = isJust (findWeights [c1, c2])++-- |Finding a weight with more or less than 2 characters results in nothing+prop_findWeightsWithNot2Letters :: String -> Property+prop_findWeightsWithNot2Letters str+ = not (length str == 2 && all isLetter str) ==> isNothing (findWeights str)++-- |Finding a weight never results in zero weights+prop_findWeightsDefaultsFreqs :: AlphaChar -> AlphaChar -> Bool+prop_findWeightsDefaultsFreqs (Alpha c1) (Alpha c2)+ = sum' weights > 0+ where weights = fromJust . findWeights $ [c1, c2]+ sum' = foldr ((+) . snd) 0
test/Test/ElocryptTest.hs view
@@ -2,124 +2,174 @@ module Test.ElocryptTest where import Control.Monad-import Control.Monad.Random hiding (next)+import Control.Monad.Random+import Data.Bool+import Data.Char import Data.List import Data.Maybe-import Test.QuickCheck hiding (frequency)+import Test.QuickCheck import Test.Tasty import Test.Tasty.QuickCheck (testProperty) import Test.Tasty.TH import Data.Elocrypt import Data.Elocrypt.Trigraph+import Test.Elocrypt.QuickCheck import Test.Elocrypt.Instances tests :: TestTree tests = $(testGroupGenerator) -prop_genPasswordShouldBeUnique :: GreaterThan2 Int -> Bool -> StdGen -> Bool-prop_genPasswordShouldBeUnique (GT2 len) caps gen- = p /= fst (genPassword len caps gen')- where (p, gen') = genPassword len caps gen+-- |Passwords have the specified length+prop_newPasswordHasLen :: Positive Int -> GenOptions -> StdGen -> Bool+prop_newPasswordHasLen (Positive len) opts gen+ = length pass == len+ where pass = newPassword len opts gen -prop_genPasswordsShouldBeUnique- :: GreaterThan2 Int- -> GreaterThan2 Int- -> Bool- -> StdGen- -> Bool-prop_genPasswordsShouldBeUnique (GT2 len) (GT2 n) caps gen- = p /= ps- where (p:ps:_) = fst (genPasswords len n caps gen)+-- |Passwords are all lowercase when caps is false+prop_newPasswordIsLower :: Positive Int -> StdGen -> Bool+prop_newPasswordIsLower (Positive len) gen+ = all isLower pass+ where pass = newPassword len opts gen+ opts = genOptions{genCapitals = False} -prop_newPasswordShouldBeLength :: Int -> Bool -> StdGen -> Property-prop_newPasswordShouldBeLength len caps gen = len > 0 ==>- length (newPassword len caps gen) == len+-- |Passwords with caps generates caps+prop_newPasswordHasCaps :: Positive Int -> StdGen -> Property+prop_newPasswordHasCaps (Positive len) gen+ = cover (any isUpper pass) 50 "has caps" $+ any isLower pass+ where pass = newPassword (len + 2) opts gen+ opts = genOptions{genCapitals = True} -prop_newPasswordShouldConsistOfAlphabet :: Int -> Bool -> StdGen -> Bool-prop_newPasswordShouldConsistOfAlphabet len caps gen- = all (`elem` alphabet) (newPassword len caps gen)+-- |Third and each successive character is taken from the trigraph+prop_3rdCharHasPositiveFrequency + :: Positive Int + -> GenOptions + -> StdGen + -> Property+prop_3rdCharHasPositiveFrequency (Positive len) opts gen+ = conjoin $ loop pass+ where pass = newPassword (len+2) opts gen+ loop (f:s:t:xs) = thirdCharIsInTrigraph [f, s, t] : loop (s:t:xs)+ loop _ = [] -prop_newPasswordsShouldBeUnique- :: GreaterThan2 Int- -> GreaterThan2 Int- -> Bool- -> StdGen- -> Bool-prop_newPasswordsShouldBeUnique (GT2 len) (GT2 n) caps gen- = p /= ps- where (p:ps:_) = newPasswords len n caps gen+thirdCharIsInTrigraph :: String -> Property+thirdCharIsInTrigraph pass+ = counterexample failMsg $ property (t `elem` candidates)+ where (f:s:t:_) = map toLower pass+ candidates = map fst . filter ((0/=) . snd) $ frequencies+ frequencies = zip ['a'..'z'] .+ defaultFrequencies .+ fromJust .+ findFrequency $ [f, s] -prop_newPasswordShouldHaveLen :: Int -> Bool -> StdGen -> Property-prop_newPasswordShouldHaveLen len caps gen- = len >= 0 ==> length (newPassword len caps gen) == len+ failMsg = t : " not in [" ++ candidates ++ "]" -prop_genPassphraseShouldBeUnique- :: GreaterThan2 Int- -> GreaterThan2 Int- -> GreaterThan0 Int+-- First 2 characters have total non-zero frequencies+prop_first2HavePositiveFrequencies + :: Positive Int+ -> GenOptions -> StdGen- -> Bool-prop_genPassphraseShouldBeUnique (GT2 n) (GT2 min) (GT0 max) gen- = not . hasDuplicates $ phrase- where phrase = fst (genPassphrase n min (min+max) gen)+ -> Property+prop_first2HavePositiveFrequencies (Positive len) opts gen+ = counterexample failMsg $ property (sum frequencies > 0)+ where pass = newPassword (len+1) opts gen+ (f:s:_) = map toLower pass+ frequencies = zipWith (curry snd) ['a'..'z'] .+ fromJust .+ findFrequency $ [f, s]+ failMsg = "no candidates for '" ++ [f, s] ++ "'" -prop_newPassphraseShouldBeUnique- :: GreaterThan2 Int- -> GreaterThan2 Int- -> GreaterThan0 Int+-- (len . fst) (genPasswords _ x _ _) = x+prop_newPasswordsHasLen+ :: Positive Int+ -> Positive Int+ -> GenOptions -> StdGen- -> Bool-prop_newPassphraseShouldBeUnique (GT2 n) (GT2 min) (GT0 max) gen- = not . hasDuplicates $ phrase- where phrase = newPassphrase n min (min+max) gen+ -> Property+prop_newPasswordsHasLen (Positive len) (Positive num) opts gen+ = counterexample failMsg $ property (length passes == num)+ where passes = newPasswords len num opts gen+ failMsg = show (length passes) ++ " /= " ++ show num -prop_newPassphraseShouldHaveLen- :: GreaterThan2 Int- -> GreaterThan2 Int- -> GreaterThan0 Int+-- (all ((x==) . length) . fst) (genPasswords x _ _ _) = x+prop_newPasswordsAllHaveLen+ :: Positive Int+ -> Positive Int+ -> GenOptions -> StdGen -> Bool-prop_newPassphraseShouldHaveLen (GT2 n) (GT2 min) (GT0 max) gen- = length phrase == n- where phrase = newPassphrase n min (min+max) gen--prop_first2ShouldHaveLength2 :: StdGen -> Bool-prop_first2ShouldHaveLength2 g = length (evalRand first2 g) == 2--prop_nextShouldSkip0Weights :: AlphaChar -> AlphaChar -> StdGen -> Property-prop_nextShouldSkip0Weights (Alpha c1) (Alpha c2) gen = isCandidate next' [c1, c2]- where next' = evalRand (next . reverse $ [c1, c2]) gen--prop_lastNShouldSkip0Weights :: AlphaChar -> AlphaChar -> Int -> StdGen -> Property-prop_lastNShouldSkip0Weights (Alpha c1) (Alpha c2) len gen- = len > 0 ==> lastNShouldSkip0Weights' lastN'- where lastN' = evalRand (lastN len [c2, c1]) gen--lastNShouldSkip0Weights' :: String -> Property-lastNShouldSkip0Weights' (p:ps:pss:psss) = isCandidate p [pss, ps] .&&.- lastNShouldSkip0Weights' (ps:pss:psss)-lastNShouldSkip0Weights' _ = property True---- Utility functions-isCandidate :: Char -> String -> Property-isCandidate c (s:ss:_) = hasCandidates f2 ==>- isJust $ elem c `liftM` findNextCandidates f2- where f2 = [s, ss]+prop_newPasswordsAllHaveLen (Positive len) (Positive num) opts gen+ = all ((len==) . length) passes+ where passes = newPasswords len num opts gen -isCandidate _ _ = undefined -- This really shouldn't ever happen+-- |Given the same generator, newPassword and genPassword generates the +-- same password.+prop_newPasswordMatchesGenPassword+ :: Positive Int+ -> GenOptions+ -> StdGen+ -> Property+prop_newPasswordMatchesGenPassword (Positive len) opts gen+ = counterexample failMsg $ property (pass == pass')+ where (pass, _) = genPassword len opts gen+ pass' = newPassword len opts gen+ failMsg = show pass ++ " /= " ++ show pass' -hasCandidates :: String -> Bool-hasCandidates s = (not . null) `liftM` findNextCandidates s == Just True+-- |Given the same generator, newPasswords and genPasswords genereates+-- the same passwords.+prop_newPasswordsMatchesGenPasswords+ :: Positive Int+ -> Positive Int+ -> GenOptions+ -> StdGen+ -> Property+prop_newPasswordsMatchesGenPasswords (Positive len) (Positive num) opts gen+ = counterexample failMsg $ property (passes == passes')+ where (passes, _) = genPasswords len num opts gen+ passes' = newPasswords len num opts gen+ failMsg = show passes ++ " /= " ++ show passes' -findNextCandidates :: String -> Maybe String-findNextCandidates (c1:c2:_) = map fst `liftM` frequency- where f2 = [c1, c2]- frequency = (filter ((/=0) . snd) . zip ['a'..'z']) `liftM` findFrequency f2+-- |newPassphrase generates n words+prop_newPassphraseHasLen + :: Positive Int+ -> Positive Int+ -> Positive Int+ -> GenOptions+ -> StdGen+ -> Property+prop_newPassphraseHasLen (Positive len) (Positive min) (Positive max) opts gen+ = counterexample failMsg $ property (length words == len)+ where words = newPassphrase len min max opts gen+ failMsg = show len ++ " /= length " ++ show words -findNextCandidates _ = Nothing -- This really shouldn't ever happen+-- |newPassphrase generates words in the allowed range+prop_newPassphraseWordsHaveLen+ :: Positive Int+ -> Positive Int+ -> Positive Int+ -> GenOptions+ -> StdGen+ -> Bool+prop_newPassphraseWordsHaveLen+ (Positive len) (Positive min) (Positive max) opts gen+ = all (\w -> length w >= min && length w <= max') words+ where words = newPassphrase len min max' opts gen+ max' = min + max -hasDuplicates = hasDuplicates' . sort- where hasDuplicates' (p1:p2:ps) | p1 == p2 = True- | otherwise = hasDuplicates' (p2:ps)- hasDuplicates' _ = False+-- |Given the same generator, genPassphrase and newPassphrase generates+-- the same passphrase+prop_genPassphraseMatchesNewPassphrase+ :: Positive Int+ -> Positive Int+ -> Positive Int+ -> GenOptions+ -> StdGen+ -> Property+prop_genPassphraseMatchesNewPassphrase + (Positive len) (Positive min) (Positive max) opts gen + = counterexample failMsg $ property (words == words')+ where (words, _) = genPassphrase len min max' opts gen+ words' = newPassphrase len min max' opts gen+ max' = min + max+ failMsg = show words ++ " /= " ++ show words'