-- A little testing framework
module Test where

import Data.List (intercalate)
import System.Exit (exitFailure)
import Test.QuickCheck hiding (Success, Failure, expectFailure)


data Expect = ExpectSuccess | ExpectFailure deriving Eq

data Test = Test String Expect Property

data Tests = Leaf Test | Node String [Tests]

testGroup :: String -> [Tests] -> Tests
testGroup = Node

expectSuccess :: Testable a => String -> a -> Tests
expectSuccess name p = Leaf $ Test name ExpectSuccess (property p)

expectFailure :: Testable a => String -> a -> Tests
expectFailure name p = Leaf $ Test name ExpectFailure (property p)

runTest :: [String] -> Test -> IO ()
runTest labels (Test name expect property) = do
    let label = intercalate "." (reverse (name : labels))
    result <- quickCheckWithResult (stdArgs { chatty = False }) property
    case (expect, isSuccess result) of
        (ExpectSuccess, True)  -> putStrLn $ "OK: " ++ label
        (ExpectFailure, False) -> putStrLn $ "OK (expected failure): " ++ label
        (ExpectSuccess, False) -> do
            putStrLn $ "\nTest failure:\n    " ++ label ++ "\n"
            putStrLn $ output result
            exitFailure
        (ExpectFailure, True) -> do
            putStrLn $ "\nUnexpected test success:\n    " ++ label ++ "\n"
            putStrLn $ output result
            exitFailure

runTests :: Tests -> IO ()
runTests = go []
  where
    go labels (Leaf test)        = runTest labels test
    go labels (Node label tests) = mapM_ (go (label : labels)) tests
