packages feed

elocrypt-0.6.0: cli-test/CliTests.hs

{-# LANGUAGE TemplateHaskell #-}
module Main where

import Data.Functor
import Data.List
import Data.Maybe
import Control.Monad
import Text.Printf

import Test.Proctest
import Test.Proctest.Assertions
import Test.QuickCheck
import Test.QuickCheck.Property
import Test.Tasty hiding (Timeout)
import Test.Tasty.QuickCheck (QuickCheckTests(..), testProperty)
import Test.Tasty.TH

import Test.Elocrypt.Instances

main :: IO ()
main = defaultMain (options tests)

options :: TestTree -> TestTree
options = localOption (QuickCheckTests 10)

tests :: TestTree
tests = testGroup "CLI Tests" [testGroup']

testGroup' = $(testGroupGenerator)

newtype CliArgs = CliArgs  { getArgs :: [String] }
               deriving Eq

instance Show CliArgs where
  show = concat . intersperse " " . getArgs

instance Arbitrary CliArgs where
  arbitrary = do
    len  <- arbitrary `suchThat` (>0) `suchThat` (<=40) :: Gen Int
    num  <- arbitrary `suchThat` (>2) `suchThat` (<=20) :: Gen Int
    args <- sublistOf ["-n %d" `printf` num,
                       show len]
    return (CliArgs args)
              
prop_shouldPrintPasswordsWithLength :: CliArgs -> Property
prop_shouldPrintPasswordsWithLength (CliArgs args)
  = isJust (getPosParam args) ==>
    ioProperty $ do
      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args
      response <- readHandle out'

      let len    = read . fromJust . getPosParam $ args
          words' = words response

      return (all ((==) len . length) words')

prop_shouldPrintNumberPasswords :: CliArgs -> Property
prop_shouldPrintNumberPasswords (CliArgs args)
  = isJust (getArg "-n" args) ==>
    ioProperty $ do
      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args
      response <- readHandle out'

      let number = read . fromJust . getArg "-n" $ args
          words' = words response
          
      return (number == length words')

prop_shouldPrintMultPasswordsPerLine :: CliArgs -> Property
prop_shouldPrintMultPasswordsPerLine (CliArgs args)
  = (read . fromMaybe "8" . getPosParam $ args) <= 38 ==>
    ioProperty $ do
      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args
      response <- readHandle out'
      return . all (>1) . tail . reverse . map length . map words . lines $ response

prop_shouldPrintDefaultMultPasswordsPerLine :: CliArgs -> Property
prop_shouldPrintDefaultMultPasswordsPerLine (CliArgs args)
  = isNothing (getArg "-n" args) ==>
    (read . fromMaybe "8" . getPosParam $ args) <= 38 ==>
    ioProperty $ do
      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args
      response <- readHandle out'
      return . all (>1) . tail . reverse . map length . map words . lines $ response

prop_shouldNotPrintZeroPasswords :: CliArgs -> Property
prop_shouldNotPrintZeroPasswords (CliArgs args)
  = isNothing (getPosParam args) ==>
    ioProperty $ do
      let args' = args ++ ["0"]
      
      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args'
      response <- readHandle out'
      return (response == "")

prop_shouldPrintLongPasswords :: GreaterThan79 Int -> Property
prop_shouldPrintLongPasswords (GT79 a)
  = ioProperty $ do
      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" [show a]
      response <- readHandle out'
      return . all (==1) . map length . map 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

assertExitedFailure :: Timeout -> ProcessHandle -> IO Bool
assertExitedFailure t = liftM not . assertExitedSuccess t

sleep' :: IO ()
sleep' = sleep (seconds 0.0001)

getArg :: String -> [String] -> Maybe String
getArg prefix args = (tail . dropWhile (not . elem')) `liftM` arg
  where arg = find (isPrefixOf prefix) args
        elem' = (flip elem) [' ', '=']

getPosParam :: [String] -> Maybe String
getPosParam = find $ (/= '-') . head