packages feed

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 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'