{-  Exploring Languages with Interpreters and Functional Programming
    Chapters 4, 8, 9, and 12: Factorial Functions Test Script
    Copyright (C) 2018, H. Conrad Cunningham

1234567890123456789012345678901234567890123456789012345678901234567890

2017-01-27: Revised for module (TestFact)
2017-02-03: Added comments
2017-08-27: Expanded testing, included beyond fact1 .. fact6
2018-07-11: Revised from previous TestFact module to test current
            Factorial module with appropriate techniques for
            2018 textbook Chapters 4, 8, 9, and 12 (testing in 12)

This uses a black-box testing method to conduct functional-type unit
tests on the fact1..fact6 functions from Chapters 4 and 9.

However, it does trap three different kinds of exceptions that might
arise in the functions depending on how they handle negative
arguments. It also tests for overflow of the fixed size integer type
Int.

Assumption: Int is represented as a 64-bit integer on the machine.

We identify two classes of inputs for the factorial function:

1. set of nonnegative integers for which the mathematical function is
   defined and the Haskell function returns that value within the
   positive `Int` range

2. set of nonnegative integers for which the mathematical function is
   defined but the Haskell function returns a value that overflows the
   `Int` range

Argument values tested include:

- 0 and 1 -- just inside lower boundary of class 1
             (both could be base cases)
- 2 and 5 -- non-boundary, representative values of class 1
- 20      -- just inside upper boundary for class 1
- 21      -- just outside upper boundary for class 1 in class 2
- -1      -- just outside lower boundary of class 1

-}

module TestFactorial
where

import Factorial

-- For catching Exceptions
import Prelude hiding (catch)
import Control.Exception

-- To display whether test passed for failed
pass :: Bool -> String
pass True  = "PASS"
pass False = "FAIL"

-- IO program test script for Factorial module black-box test
-- Does not depend on internal details (except errors generated)
main :: IO ()
main =
    do
        -- Test function fact1
        -- values inside boundary of class 1
        putStrLn  "\nTesting fact1"
        putStrLn ("fact1 0 == 1:                          "
                  ++ pass (fact1 0 == 1))
        putStrLn ("fact1 1 == 1:                          "
                  ++ pass (fact1 1 == 1))
        putStrLn ("fact1 2 == 2:                          "
                  ++ pass (fact1 2 == 2)) 
        putStrLn ("fact1 5 == 120:                        "
                  ++ pass (fact1 5 == 120)) 
        putStrLn ("fact1 20 == 2432902008176640000:       "
                  ++ pass (toInteger (fact1 20) ==
                           2432902008176640000))
        -- values outside boundary of class 1
        putStrLn (
                  "fact1 21 == 51090942171709440000:      "
            ++ pass (toInteger (fact1 21) == 51090942171709440000)
            ++ " (EXPECT FAIL for 64-bit Int)" )
        putStrLn ("fact1 (-1)  == 1:                      "
                  ++ pass (fact1 (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow] (EXPECTED)"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn ("[Pattern Match Failure]\n...."
                                      ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n...."
                                      ++ msg))

        -- Test function fact2
        -- values inside boundary of class 1
        putStrLn  "\nTesting fact2"  
        putStrLn ("fact2 0 == 1:                          "
                  ++ pass (fact2 0 == 1))
        putStrLn ("fact2 1 == 1:                          "
                  ++ pass (fact2 1 == 1))
        putStrLn ("fact2 2 == 2:                          "
                  ++ pass (fact2 2 == 2))
        putStrLn ("fact2 5 == 120:                        "
                  ++ pass (fact2 5 == 120))
        putStrLn ("fact2 20 == 2432902008176640000:       "
                  ++ pass (toInteger (fact2 20) ==
                           2432902008176640000))
        -- values outside boundary of class 1
        putStrLn ("fact2 21 == 51090942171709440000:      "
            ++ pass (toInteger (fact2 21) == 51090942171709440000)
            ++ " (EXPECT FAIL for 64-bit Int)" )
        putStrLn ("fact2 (-1)  == 1:                      "
                  ++ pass (fact1 (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow] (EXPECTED)"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn ("[Pattern Match Failure]\n...."
                                      ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n...." ++ msg))

        -- Test function fact3
        -- values inside boundary of class 1
        putStrLn  "\nTesting fact3"
        putStrLn ("fact3 0 == 1:                          "
                  ++ pass (fact3 0 == 1))
        putStrLn ("fact3 1 == 1:                          "
                  ++ pass (fact3 1 == 1))
        putStrLn ("fact3 2 == 2:                          "
                  ++ pass (fact3 2 == 2))
        putStrLn ("fact3 5 == 120:                        "
                  ++ pass (fact3 5 == 120))
        putStrLn ("fact3 20 == 2432902008176640000:       "
                  ++ pass (toInteger (fact3 20) ==
                           2432902008176640000))
        -- values outside boundary of class 1
        putStrLn ("fact3 21 == 51090942171709440000:      "
            ++ pass (toInteger (fact3 21) == 51090942171709440000)
            ++ " (EXPECT FAIL for 64-bit Int)" )
        putStrLn ("fact3 (-1)  == 1:                      "
                  ++ pass (fact3 (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow] (EXPECTED)"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn ("[Pattern Match Failure]\n...."
                                      ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n...." ++ msg))

        -- Test function fact4
        -- values inside boundary of class 1
        putStrLn  "\nTesting fact4"
        putStrLn ("fact4 0 == 1:                          "
                  ++ pass (fact4 0 == 1))
        putStrLn ("fact4 1 == 1:                          "
                  ++ pass (fact4 1 == 1)) 
        putStrLn ("fact4 2 == 2:                          "
                  ++ pass (fact4 2 == 2))
        putStrLn ("fact4 5 == 120:                        "
                  ++ pass (fact4 5 == 120))
        putStrLn ("fact4 20 == 2432902008176640000:       "
                  ++ pass (toInteger (fact4 20) ==
                           2432902008176640000))
        -- values outside boundary of class 1
        putStrLn ("fact4 21 == 51090942171709440000:      "
                  ++ pass (toInteger (fact4 21) ==
                           51090942171709440000)
            ++ " (EXPECT FAIL for 64-bit Int)" )
        putStrLn ("fact4 (-1)  == 1:                      "
                  ++ pass (fact4  (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow]"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn (
                            "[Pattern Match Failure] (EXPECTED)\n...."
                            ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n...." ++ msg))

        -- Test function fact4'
        -- values inside boundary of class 1
        putStrLn  "\nTesting fact4'"
        putStrLn ("fact4' 0 == 1:                         "
                  ++ pass (fact4' 0 == 1))
        putStrLn ("fact4' 1 == 1:                         "
                  ++ pass (fact4' 1 == 1))
        putStrLn ("fact4' 2 == 2:                         "
                  ++ pass (fact4' 2 == 2))
        putStrLn ("fact4' 5 == 120:                       "
                  ++ pass (fact4' 5 == 120))
        putStrLn ("fact4' 20 == 2432902008176640000:      "
                  ++ pass (toInteger (fact4' 20) ==
                           2432902008176640000))
        -- values outside boundary of class 1
        putStrLn ("fact4' 21 == 51090942171709440000:     "
            ++ pass (toInteger (fact4' 21)==51090942171709440000)
             ++ " (EXPECT FAIL for 64-bit Int)" )
        putStrLn ("fact4' (-1)  == 1:                     "
                  ++ pass (fact4' (-1) == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow]"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn ("[Pattern Match Failure]\n "
                                      ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call] (EXPECTED)\n...."
                                      ++ msg))

        -- Test function fact4''
        -- values inside boundary of class 1
        putStrLn  "\nTesting fact4''"
        putStrLn ("fact4'' 0 == 1:                        "
                  ++ pass (fact4'' 0 == 1))
        putStrLn ("fact4'' 1 == 1:                        "
                  ++ pass (fact4'' 1 == 1))
        putStrLn ("fact4'' 2 == 2:                        "
                  ++ pass (fact4'' 2 == 2))
        putStrLn ("fact4'' 5 == 120:                      "
                  ++ pass (fact4'' 5 ==120))
        putStrLn ("fact4'' 20 == 2432902008176640000:     "
                  ++ pass (toInteger (fact4'' 20) ==
                           2432902008176640000))
        -- values outside boundary of class 1
        putStrLn ("fact4'' 21 == 51090942171709440000:    "
            ++ pass (toInteger (fact4'' 21) == 51090942171709440000)
            ++ " (EXPECT FAIL for 64-bit Int)" )
        putStrLn ("fact4'' (-1) == 1:                     "
                  ++ pass (fact4'' (-1) == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow]"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn ("[Pattern Match Failure]\n...."
                                      ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call] (EXPECTED)\n...."
                                      ++ msg))

        -- Test function fact5
        -- values inside boundary of class 1
        putStrLn  "\nTesting fact5"
        putStrLn ("fact5 0 == 1:                          "
                  ++ pass (fact5 0 == 1))
        putStrLn ("fact5 1 == 1:                          "
                  ++ pass (fact5 1 == 1))
        putStrLn ("fact5 2 == 2:                          "
                  ++ pass (fact5 2 == 2))
        putStrLn ("fact5 5 == 120:                        "
                  ++ pass (fact5 5 == 120))
        putStrLn ("fact5 20 == 2432902008176640000:       "
                  ++ pass (toInteger (fact5 20) ==
                           2432902008176640000))
        -- values outside boundary of class 1
        putStrLn ("fact5 21 == 51090942171709440000:      "
                  ++ pass (toInteger (fact5 21) ==
                           51090942171709440000)
                  ++ " (EXPECT FAIL for 64-bit Int)" )
        putStrLn ("fact5 (-1)  == 1:                      "
                  ++ pass (fact5 (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow]"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn ("[Pattern Match Failure]\n..."
                                      ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n..." ++ msg))

        -- Test function fact6
        -- values inside boundary of class 1
        putStrLn  "\nTesting fact6"
        putStrLn ("fact6 0 == 1:                          "
                  ++ pass (fact6 0 == 1))
        putStrLn ("fact6 1 == 1:                          "
                  ++ pass (fact6 1 == 1))
        putStrLn ("fact6 2 == 2:                          "
                  ++ pass (fact6 2 == 2))
        putStrLn ("fact6 5 == 120:                        "
                  ++ pass (fact6 5 == 120))
        putStrLn ("fact6 20 == 2432902008176640000:       "
                  ++ pass (toInteger (fact6 20) ==
                           2432902008176640000))
        -- values outside boundary of class 1
        putStrLn ("fact6 21 == 51090942171709440000:      "
            ++ pass (toInteger (fact6 21) == 51090942171709440000)
            ++ " (EXPECT FAIL for 64-bit Int)" )
        putStrLn ("fact6 (-1)  == 1:                      "
                  ++ pass (fact6 (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow]"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn (
                            "[Pattern Match Failure] (EXPECTED)\n...."
                            ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n...." ++ msg))

        -- Test function fact7
        -- values inside boundary of class 1
        putStrLn  "\nTesting fact7 (returns Maybe)"
        putStrLn ("fact7 0 == Just 1:                     "
                  ++ pass (fact7 0 == Just 1))
        putStrLn ("fact7 1 == Just 1:                     "
                  ++ pass (fact7 1 == Just 1))
        putStrLn ("fact7 2 == Just 2:                     "
                  ++ pass (fact7 2 == Just 2))
        putStrLn ("fact7 5 == Just 120:                   "
                  ++ pass (fact7 5 == Just 120))
        putStrLn ("fact7 20 == Just 2432902008176640000:  "
            ++ pass (fmap toInteger (fact7 20) ==
                     Just 2432902008176640000))
        -- values outside boundary of class 1
        putStrLn (
          "fmap toInteger (fact7 21) == Just 51090942171709440000:\n"
          ++      "                                       "
          ++ pass (fmap toInteger (fact7 21) ==
                     Just 51090942171709440000 )
          ++ " (Should Fail for 64-bit Int)")
        putStrLn ("fact7 (-1)  == Nothing:                "
                  ++ pass (fact7 (-1)  == Nothing))

        putStrLn  "\nTesting fact8"
        putStrLn ("fact8 0 == 1:                          "
                  ++ pass (fact8 0 == 1))
        putStrLn ("fact8 1 == 1:                          "
                  ++ pass (fact8 1 == 1))
        putStrLn ("fact8 2 == 2:                          "
                  ++ pass (fact8 2 == 2))
        putStrLn ("fact8 5 == 120:                        "
                  ++ pass (fact8 5 == 120))
        putStrLn ("fact8 20 == 2432902008176640000:       "
                  ++ pass (fact8 20 == 2432902008176640000))
        putStrLn ("fact8 21 == 51090942171709440000:      "
                  ++ pass (fact8 21 == 51090942171709440000))
        putStrLn ("fact8 (-1)  == 1:                      "
                  ++ pass (fact8 (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow]"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn (
                            "[Pattern Match Failure] (EXPECTED)\n...."
                            ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n...." ++ msg))

        putStrLn  "\nTesting fact9"
        putStrLn ("fact9 0 == 1:                          "
                  ++ pass (fact9 0 == 1))
        putStrLn ("fact9 1 == 1:                          "
                  ++ pass (fact9 1 == 1))
        putStrLn ("fact9 2 == 2:                          "
                  ++ pass (fact9 2 == 2))
        putStrLn ("fact9 5 == 120:                        "
                  ++ pass (fact9 5 == 120))
        putStrLn ("fact9 20 == 2432902008176640000:       "
                  ++ pass (fact9 20 == 2432902008176640000))
        putStrLn ("fact9 21 == 51090942171709440000:      "
                  ++ pass (fact9 21 == 51090942171709440000))
        putStrLn ("fact9 (-1)  == 1:                      "
                  ++ pass (fact9 (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow]"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn ("[Pattern Match Failure]\n...,"
                                      ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n...." ++ msg))

        putStrLn  "\nTesting fact10"
        putStrLn ("fact10 0 == 1:                         "
                  ++ pass (fact10 0 == 1))
        putStrLn ("fact10 1 == 1:                         "
                  ++ pass (fact10 1 == 1))
        putStrLn ("fact10 2 == 2:                         "
                  ++ pass (fact10 2 == 2))
        putStrLn ("fact10 5 == 120:                       "
                  ++ pass (fact10 5 == 120))
        putStrLn ("fact10 20 == 2432902008176640000:      "
                  ++ pass (fact10 20 == 2432902008176640000))
        putStrLn ("fact10 21 == 51090942171709440000:     "
                  ++ pass (fact10 21 == 51090942171709440000))
        putStrLn ("fact10 (-1)  == 1:                     "
                  ++ pass (fact10 (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow]"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn ("[Pattern Match Failure]\n...."
                                      ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n...."
                                      ++ msg))

        putStrLn  "\nTesting fact11"
        putStrLn ("fact11 0 == 1:                         "
                  ++ pass (fact11 0 == 1))
        putStrLn ("fact11 1 == 1:                         "
                  ++ pass (fact11 1 == 1))
        putStrLn ("fact11 2 == 2:                         "
                  ++ pass (fact11 2 == 2))
        putStrLn ("fact11 5 == 120:                       "
                  ++ pass (fact11 5 == 120))
        putStrLn ("fact11 20 == 2432902008176640000:      "
                  ++ pass (fact11 20 == 2432902008176640000))
        putStrLn ("fact11 21 == 51090942171709440000:     "
                  ++ pass (fact11 21 == 51090942171709440000))
        putStrLn ("fact11 (-1)  == 1:                     "
                  ++ pass (fact11 (-1)  == 1))
            `catch` (\(StackOverflow)
                         -> putStrLn ("[Stack Overflow]"))
            `catch` (\(PatternMatchFail msg)
                         -> putStrLn ("[Pattern Match Failure]\n...."
                                      ++ msg))
            `catch` (\(ErrorCall msg)
                         -> putStrLn ("[Error Call]\n...." ++ msg))
