-- pa3_test.hs
-- Glenn G. Chappell
-- Version 2
-- 5 Mar 2009
--
-- For CS 331 Spring 2009
-- Test Program for Assignment 3 Functions & Variables
-- Used in Assignment 3, Exercise A

module Main where

import PA3  -- For Assignment 3 Functions & Variables


------------------------------------------------------------------------
-- Testing Package
------------------------------------------------------------------------


-- TestState a
-- Data type for holding results of tests
-- First item in pair is a Maybe giving pass/fail results so far:
--     Just _   means all passed so far
--     Nothing  means at least one failure so far
-- Second item in pair is IO for output of tests
data TestState a = TS (Maybe a, IO a)

-- Accessor functions for parts of a TestState value
-- For convenience
tsMaybe (TS (x, y)) = x
tsIO (TS (x, y)) = y

-- Make TestState a monad in the obvious way
instance Monad TestState where
    return a = TS (return a, return a)
    TS (x, y) >>= f = TS (x >>= f1, y >>= f2) where
        f1 = tsMaybe . f
        f2 = tsIO . f

-- testMsg
-- Print a message (e.g., "Test Suite: ...") in TestState monad
testMsg :: String -> TestState ()
testMsg str = TS (Just (), putStrLn str)

-- test
-- Do test in TestState monad
-- Given result of test (Bool: True if passed) & description of test
-- Adds result of test & description + pass/fail output to monadic value
test :: Bool -> String -> TestState ()
test success descrip = TS (theMaybe, putStrLn fullDescrip) where
    theMaybe  | success    = Just ()
              | otherwise  = Nothing
    fullDescrip = "    Test: " ++ descrip ++ " - " ++ passFailStr
    passFailStr  | success    = "passed"
                 | otherwise  = "********** FAILED **********"

-- testEq
-- Like test, but given 2 values, checks whether they are equal
testEq :: Eq a => a -> a -> String -> TestState ()
testEq a b str = test (a == b) str

-- printResults
-- Converts TestState monadic value to IO monadic value
--  with summary of all test results
printResults :: TestState () -> IO ()
printResults z = do
    -- Do IO from tests
    tsIO z
    putStrLn ""
    -- Then output summary: all passed or not
    if tsMaybe z == Nothing
        then putStrLn "Tests ********** UNSUCCESSFUL **********"
        else putStrLn "All tests successful"


------------------------------------------------------------------------
-- Test Suites
------------------------------------------------------------------------
    

-- test_evens
-- Test Suite for function evens
test_evens = do
    testMsg "Test Suite: Function evens"
    testEq (evens [5,2,4,7,3,5]) [5,4,3] "evens, Medium-sized list #1"
    testEq (evens [5,2,4,7,3,5,0]) [5,4,3,0] "evens, Medium-size list #2"
    testEq (evens [1..500]) [1,3..500] "evens, Large list"
    testEq (evens [5]) [5] "evens, Singleton list"
    testEq (evens "Howdy, there!") "Hwy hr!" "evens, String"
    testEq (evens "") "" "evens, Empty list"

-- test_max2
-- Test Suite for function max2
test_max2 = do
    testMsg "Test Suite: Function max2"
    testEq (max2 [5,2,4,7,3,1]) [5,7] "max2, Medium-sized list #1"
    testEq (max2 [7,2,4,7,3,5]) [7,7] "max2, Medium-sized list #2"
    testEq (max2 [1..500]) [499,500] "max2, Large list"
    testEq (max2 [5]) [5] "max2, Singleton list"
    testEq (max2 "Howdy, there!") "wy" "max2, String"
    testEq (max2 "") "" "max2, Empty list"

-- test_almostMax
-- Test Suite for function almostMax
test_almostMax = do
    testMsg "Test Suite: Function almostMax"
    testEq (almostMax [5,2,4,7,3,1]) [5] "almostMax, Medium-sized list #1"
    testEq (almostMax [7,2,4,7,3,5]) [7] "almostMax, Medium-sized list #2"
    testEq (almostMax [1..500]) [499] "almostMax, Large list"
    testEq (almostMax [5]) [] "almostMax, Singleton list"
    testEq (almostMax "Howdy, there!") "w" "almostMax, String"
    testEq (almostMax "") "" "almostMax, Empty list"

-- test_primes
-- Test Suite for variable primes
test_primes = do
    testMsg "Test Suite: Variable primes [NOTE: MAY BE SLOW]"
    testEq (take 200 primes) primes200 "primes, first 200 values"
    testEq (primes !! 300) 1993 "primes, value @ index 300"
        where
        -- primes200: first 200 primes
        primes200 = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,
            67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,
            149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,
            229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,
            313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,
            409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,
            499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,
            601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,
            691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,
            809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,
            907,911,919,929,937,941,947,953,967,971,977,983,991,997,
            1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,
            1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,
            1171,1181,1187,1193,1201,1213,1217,1223]

-- allTests
-- Run all test suites for Assignment 3 functions & variables
allTests = do
    testMsg "TEST SUITES FOR ASSIGNMENT 3 FUNCTIONS & VARIABLES"
    test_evens
    test_max2
    test_almostMax
    test_primes

main = printResults allTests

