{-# LANGUAGE OverloadedStrings #-} module Test.Chell.Types where import qualified Control.Exception import Control.Exception (SomeException) import qualified Data.Text import Data.Text (Text) import System.Timeout (timeout) data Test = Test Text (TestOptions -> IO TestResult) instance Show Test where show (Test name _) = "" data TestOptions = TestOptions { testOptionSeed_ :: Int , testOptionTimeout_ :: Maybe Int } deriving (Show, Eq) -- | @testName (Test name _) = name@ testName :: Test -> Text testName (Test name _) = name -- | Run a test, wrapped in error handlers. This will return 'TestAborted' if -- the test throws an exception or times out. runTest :: Test -> TestOptions -> IO TestResult runTest (Test _ io) options = handleJankyIO options (io options) (return []) handleJankyIO :: TestOptions -> IO TestResult -> IO [(Text, Text)] -> IO TestResult handleJankyIO opts getResult getNotes = do let withTimeout = case testOptionTimeout_ opts of Just time -> timeout (time * 1000) Nothing -> fmap Just let hitTimeout = Data.Text.pack str where str = "Test timed out after " ++ show time ++ " milliseconds" Just time = testOptionTimeout_ opts let errorExc :: SomeException -> Text errorExc exc = Data.Text.pack ("Test aborted due to exception: " ++ show exc) tried <- withTimeout (Control.Exception.try getResult) case tried of Just (Right ret) -> return ret Nothing -> do notes <- getNotes return (TestAborted notes hitTimeout) Just (Left exc) -> do notes <- getNotes return (TestAborted notes (errorExc exc)) data TestResult = TestPassed [(Text, Text)] | TestSkipped | TestFailed [(Text, Text)] [Failure] | TestAborted [(Text, Text)] Text deriving (Show, Eq) data Failure = Failure (Maybe Location) Text deriving (Show, Eq) data Location = Location { locationFile :: Text , locationModule :: Text , locationLine :: Integer } deriving (Show, Eq) -- | A tree of 'Test's; use the 'test' and 'suite' helper functions to build -- up a 'Suite'. data Suite = Suite Text [Suite] | SuiteTest Test instance Show Suite where show s = "" test :: Test -> Suite test = SuiteTest suite :: Text -> [Suite] -> Suite suite = Suite suiteName :: Suite -> Text suiteName (Suite name _) = name suiteName (SuiteTest t) = testName t -- | The full list of 'Test's contained within this 'Suite'. Each 'Test' -- is returned with its name modified to include the name of its parent -- 'Suite's. suiteTests :: Suite -> [Test] suiteTests = loop "" where loop prefix s = let name = if Data.Text.null prefix then suiteName s else Data.Text.concat [prefix, ".", suiteName s] in case s of Suite _ suites -> concatMap (loop name) suites SuiteTest (Test _ io) -> [Test name io]