husky-0.3: test/CalculatorTest.hs
{-----------------------------------------------------------------
(c) 2008-2009 Markus Dittrich
This program is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public
License Version 3 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License Version 3 for more details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
--------------------------------------------------------------------}
-- | handcoded test for checking out the calculator parser
module Main where
-- import
import Control.Monad.Writer
import System.Exit
-- local imports
import Parser
import CalculatorState
import ExtraFunctions
import PrettyPrint
import TokenParser
-- | top level main routine
-- we use the Writer monad to capture the results for all tests
-- and then examine the results afterward
main :: IO ()
main = do
putStrLn "\n\n\nTesting calculator Parser ..."
putStr $ color_string Cyan "\nSimple tests:\n"
let simple = execWriter $ good_test_driver defaultCalcState
simpleTests
status1 <- examine_output simple
putStr $ color_string Cyan "\nVariable tests:\n"
let vars = execWriter $ good_test_driver defaultCalcState
variableTests
status2 <- examine_output vars
putStr $ color_string Cyan "\nFailure tests:\n"
let failing = execWriter $ failing_test_driver defaultCalcState
failingTests
status2 <- examine_output failing
let status = status1 && status2
if status == True then
exitWith ExitSuccess
else
exitWith $ ExitFailure 1
-- | helper function for examining the output of a good test run
-- (i.e. one that should succeed), prints out the result for each
-- test, collects the number of successes/failures and returns
-- True in case all tests succeeded and False otherwise
examine_output :: [TestResult] -> IO Bool
examine_output = foldM examine_output_h True
where
examine_output_h :: Bool -> TestResult -> IO Bool
examine_output_h acc (TestResult status token target actual) = do
if status == True then do
putStr $ color_string Blue "["
putStr $ color_string White "OK"
putStr $ color_string Blue "] "
putStr $ color_string Green " Successfully evaluated "
putStrLn $ color_string Yellow token
return $ acc && True
else do
putStr $ color_string Blue "["
putStr $ color_string Red "TROUBLE"
putStr $ color_string Blue "] "
putStr $ color_string Green " Failed to evaluate "
putStrLn $ color_string Yellow token
putStrLn $ color_string Green "\t\texpected : "
++ (show target)
putStrLn $ color_string Green "\t\tgot : "
++ (show actual)
return False
-- | main test routine for "good tests"
good_test_driver :: CalcState -> [GoodTestCase]
-> Writer [TestResult] ()
good_test_driver _ [] = return ()
good_test_driver state (x:xs) = do
let tok = fst x
let expected = snd x
case runParser main_parser state "" tok of
Left er -> tell [TestResult False tok (show expected) (show er)]
Right ((result,_), newState) -> examine_result expected result tok
where
-- NOTE: when we compare target and actual result we
-- probably need to be more careful and can't use ==
-- if we are dealing with Doubles!!!
examine_result :: Double -> Double -> String
-> Writer [TestResult] ()
examine_result target actual tok =
if (is_equal target actual)
then do
tell [TestResult True tok (show target) (show actual)]
good_test_driver newState xs
else do
tell [TestResult False tok (show target) (show actual)]
good_test_driver newState xs
-- | main test routine for "failing tests"
failing_test_driver :: CalcState -> [FailingTestCase]
-> Writer [TestResult] ()
failing_test_driver _ [] = return ()
failing_test_driver state (x:xs) = do
case runParser main_parser state "" x of
Left er -> tell [TestResult True x "Failure" "Failure"]
>> failing_test_driver state xs
Right _ -> tell [TestResult False x "Failure" "Success"]
-- | our test results consist of a bool indicating success
-- or failure, the test token as well as the expected and
-- received result
data TestResult = TestResult { status :: Bool
, token :: String
, target :: String
, actual :: String
}
defaultResult :: TestResult
defaultResult = TestResult False "" "" ""
-- | a good test case consists of an expression and an
-- expected result
type GoodTestCase = (String, Double)
-- | a failing test case currently consists only of an
-- expression to be parser and we simply tests if it
-- fails as expected.
-- FIXME:
-- In principle, we should check for the correct failure
-- message. However, since I am still playing with the parser
-- these may change so for now we just check for failure.
type FailingTestCase = String
-- NOTE: For each "run" of test_driver we thread a common
-- calculator state to be able to test variable assignment
-- and use. Therefore, the order of which tests appear in
-- a [GoodTestCase] may matter if variable definitions are involved.
-- I.e., think twice when changing the order, or keep order
-- dependend and independent sets in different lists
simpleTests :: [GoodTestCase]
simpleTests = [ simpleTest1, simpleTest2, simpleTest3, simpleTest4
, simpleTest5, simpleTest6, simpleTest7
, simpleTest8, simpleTest9, simpleTest10, simpleTest11
, simpleTest12, simpleTest13, simpleTest14
, simpleTest15, simpleTest16, simpleTest17
, simpleTest18, simpleTest19, simpleTest20
, simpleTest21, simpleTest22, simpleTest23
, simpleTest24, simpleTest25, simpleTest26]
-- list of simple tests
simpleTest1 :: GoodTestCase
simpleTest1 = ("3+4", 7.0)
simpleTest2 :: GoodTestCase
simpleTest2 = ("3*3", 9.0)
simpleTest3 :: GoodTestCase
simpleTest3 = ("(3*3)+(3*4)", 21.0)
simpleTest4 :: GoodTestCase
simpleTest4 = ("(3.0*3.0)+(3.0*4.0)", 21.0)
simpleTest5 :: GoodTestCase
simpleTest5 = ("(3+3)*(9+8)", 102.0)
simpleTest6 :: GoodTestCase
simpleTest6 = ("(3.0+3.0)*(9.0+8.0)", 102.0)
simpleTest7 :: GoodTestCase
simpleTest7 = ("(((((((3.0+3.0)*(9.0+8.0)))))))", 102.0)
simpleTest8 :: GoodTestCase
simpleTest8 = ("(((((((3.0+3.0)))))*(((((9.0+8.0)))))))", 102.0)
simpleTest9 :: GoodTestCase
simpleTest9 = ("3+3*99.0", 300.0)
simpleTest10 :: GoodTestCase
simpleTest10 = ("3+3*8+4*3*2+1*4*3+5", 68.0)
simpleTest11 :: GoodTestCase
simpleTest11 = ("(3+3)*(8+4)*3*(2+1)*4*(3+5)", 20736.0)
simpleTest12 :: GoodTestCase
simpleTest12 = (" 3 +3* 99.0", 300.0)
simpleTest13 :: GoodTestCase
simpleTest13 = (" 3 + 3*8+4 *3 *2+1* 4*3+5 ", 68.0)
simpleTest14 :: GoodTestCase
simpleTest14 = ("(3+3) *(8+4)*3 * (2+1 )*4*( 3+5)", 20736.0)
simpleTest15 :: GoodTestCase
simpleTest15 = ("3*-4", -12.0)
simpleTest16 :: GoodTestCase
simpleTest16 = ("3* -4", -12.0)
simpleTest17 :: GoodTestCase
simpleTest17 = ("-3*4", -12.0)
simpleTest18 :: GoodTestCase
simpleTest18 = ("-3*-4", 12.0)
simpleTest19 :: GoodTestCase
simpleTest19 = ("3*(-4)", -12.0)
simpleTest20 :: GoodTestCase
simpleTest20 = ("(-3)*(-4)", 12.0)
simpleTest21 :: GoodTestCase
simpleTest21 = ("3/-4", -0.75)
simpleTest22 :: GoodTestCase
simpleTest22 = ("3^-4", 1/81)
simpleTest23 :: GoodTestCase
simpleTest23 = ("-3*-4^-4", -3/256)
simpleTest24 :: GoodTestCase
simpleTest24 = ("-3+-4", -7)
simpleTest25 :: GoodTestCase
simpleTest25 = ("-1/-1/-1/-1", 1.0)
simpleTest26 :: GoodTestCase
simpleTest26 = ("-(-(-1))", -1)
-- a few tests involving variables
variableTests :: [GoodTestCase]
variableTests = [ variableTest1, variableTest2, variableTest3
, variableTest4, variableTest5, variableTest6
, variableTest7, variableTest8, variableTest9
, variableTest10, variableTest11, variableTest12 ]
-- list of failing tests
variableTest1 :: GoodTestCase
variableTest1 = ("b = 4", 4)
variableTest2 :: GoodTestCase
variableTest2 = ("3 * b ", 12)
variableTest3 :: GoodTestCase
variableTest3 = ("(b*b)", 16)
variableTest4 :: GoodTestCase
variableTest4 = ("a = 12", 12)
variableTest5 :: GoodTestCase
variableTest5 = ("a * b", 48)
variableTest6 :: GoodTestCase
variableTest6 = ("a - b * b", (-4))
variableTest7 :: GoodTestCase
variableTest7 = ("3 * b - a", 0)
variableTest8 :: GoodTestCase
variableTest8 = ("kjhdskfsd123hjksdf = a * b", 48)
variableTest9 :: GoodTestCase
variableTest9 = ("(a*b) - kjhdskfsd123hjksdf", 0)
variableTest10 :: GoodTestCase
variableTest10 = ("c = 2", 2)
variableTest11 :: GoodTestCase
variableTest11 = ("a-b-c + ( a + b + c ) + (a*a)", 168)
variableTest12 :: GoodTestCase
variableTest12 = ("b^a - c", 16777214)
-- a few tests that are failing
failingTests :: [FailingTestCase]
failingTests = [ failingTest1, failingTest2, failingTest3
, failingTest4, failingTest5, failingTest6
, failingTest7, failingTest8, failingTest9
, failingTest10, failingTest11, failingTest12 ]
-- list of failing tests
failingTest1 :: FailingTestCase
failingTest1 = ("3+4b")
failingTest2 :: FailingTestCase
failingTest2 = ("3*a3")
failingTest3 :: FailingTestCase
failingTest3 = ("(3*3)B+(3*4)")
failingTest4 :: FailingTestCase
failingTest4 = ("(3.0*3.0)+3.0*4.0)")
failingTest5 :: FailingTestCase
failingTest5 = ("(3y3)*(9+8)")
failingTest6 :: FailingTestCase
failingTest6 = ("(3.0+3.0)*(9.0+8.0")
failingTest7 :: FailingTestCase
failingTest7 = ("(((((((3.0+3.0)*(9.0+8.0))))))")
failingTest8 :: FailingTestCase
failingTest8 = ("(((((((3.0+3.0))))*((((((9.0+8.0)))))))")
failingTest9 :: FailingTestCase
failingTest9 = ("a3+3*99.0")
failingTest10 :: FailingTestCase
failingTest10 = ("3+3*8+4*3++2+1*4*3+5")
failingTest11 :: FailingTestCase
failingTest11 = ("(3+3)**(8+4)*3*(2+1)*4*(3+5)")
failingTest12 :: FailingTestCase
failingTest12 = ("b")