packages feed

elocrypt-2.1.0: test/Test/Elocrypt/UtilsTest.hs

{-# LANGUAGE TemplateHaskell #-}
module Test.Elocrypt.UtilsTest where

import Control.Monad.Random
import Data.Maybe (isNothing)
import Data.List (find, elem, sort)
import Data.Ratio ((%))
import qualified Data.Map as M

import Test.QuickCheck
import Test.QuickCheck.Monadic (monadicIO)
import Test.Tasty
import Test.Tasty.QuickCheck (testProperty)
import Test.Tasty.TH

import Data.Elocrypt.Utils
import Test.Elocrypt.QuickCheck
import Test.Elocrypt.Instances

tests :: TestTree
tests = $(testGroupGenerator)

-- |Maps letters to all digits 0-9
prop_numeralConversionsHasAllDigits :: Bool
prop_numeralConversionsHasAllDigits
  = sort digits == ['0'..'9']
  where digits = M.foldr (++) [] numeralConversions

-- |toDigit never returns a letter that looks like a number
prop_toDigitDoesntLookLikeNumber :: Char -> Bool
prop_toDigitDoesntLookLikeNumber c
  = isNothing $ find (`elem` candidates) c'
  where candidates = M.keys numeralConversions
        c' = toDigit c

-- |toSymbol never returns a letter that looks like a symbol
prop_toSymbolDoesntLookLikeSymbol :: Char -> Bool
prop_toSymbolDoesntLookLikeSymbol c
  = isNothing $ find (`elem` M.keys symbolConversions) c'
  where c' = toSymbol c

prop_toSymbolReturnsSymbols :: Bool
prop_toSymbolReturnsSymbols = all isSymbol ss
  where ks = M.keys symbolConversions
        ss = concatMap toSymbol ks

-- |isSymbol returns false for letters and numbers
prop_isSymbolAlphaNumReturnsFalse :: Char -> Property
prop_isSymbolAlphaNumReturnsFalse c
  = c `elem` (['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9']) ==> not (isSymbol c)

prop_isSymbolWhitespaceReturnsFalse :: Bool
prop_isSymbolWhitespaceReturnsFalse = all (not . isSymbol) " \n"

-- |updateR always updates when prob = 1/0
prop_updateRAlwaysUpdates :: String -> StdGen -> Bool
prop_updateRAlwaysUpdates s = evalRand $ do
  let f _ = return '*'
      prob = 99999 % 1 -- Make prob absurdly high since we can't use 0 denoms
  s' <- updateR f prob s

  -- Make sure almost all letters are '*'
  return $ length (filter (=='*') s') >= length s - 1

-- |updateR never updates when prob = 0/1
prop_updateRNeverUpdates :: String -> StdGen -> Bool
prop_updateRNeverUpdates s = evalRand $ do
  let f _ = return '*'
      prob = 0 % 1
  s' <- updateR f prob s

  return $ s == s'

-- |update1 Always updates at least one character
prop_update1AlwaysUpdates1 :: String -> Property
prop_update1AlwaysUpdates1 s = not (null s) ==> do
  let f = (const . return) '*'
  
  pos <- choose (0, length s - 1)
  s'  <- update1 f s pos

  return $ elem '*' s'

-- |update1 allows empty string
prop_update1DoesntCrashOnEmpty :: Property
prop_update1DoesntCrashOnEmpty = monadicIO $ do
  let f _ = return '*'
  s <- update1 f "" 0

  return (s == "")