module Test.Hspec.Discover (
Spec
, hspec
, IsFormatter (..)
, hspecWithFormatter
, postProcessSpec
, describe
, module Prelude
) where
import Prelude hiding (mapM)
import Control.Applicative
import Data.Maybe
import Data.List
import Data.Traversable
import Control.Monad.Trans.State
import Test.Hspec.Core.Spec
import Test.Hspec.Core.Runner
import Test.Hspec.Formatters
import Test.Hspec.Core.Util (safeTry)
class IsFormatter a where
toFormatter :: a -> IO Formatter
instance IsFormatter (IO Formatter) where
toFormatter = id
instance IsFormatter Formatter where
toFormatter = return
hspecWithFormatter :: IsFormatter a => a -> Spec -> IO ()
hspecWithFormatter formatter spec = do
f <- toFormatter formatter
hspecWith defaultConfig {configFormatter = Just f} spec
postProcessSpec :: FilePath -> Spec -> Spec
postProcessSpec = locationHeuristicFromFile
locationHeuristicFromFile :: FilePath -> Spec -> Spec
locationHeuristicFromFile file spec = do
mInput <- either (const Nothing) Just <$> (runIO . safeTry . readFile) file
let lookupLoc = maybe (\_ _ _ -> Nothing) (lookupLocation file) mInput
runIO (runSpecM spec) >>= fromSpecList . addLoctions lookupLoc
addLoctions :: (Int -> Int -> String -> Maybe Location) -> [SpecTree a] -> [SpecTree a]
addLoctions lookupLoc = map (fmap f) . enumerate
where
f :: ((Int, Int), Item a) -> Item a
f ((n, total), item) = item {itemLocation = itemLocation item <|> lookupLoc n total (itemRequirement item)}
type EnumerateM = State [(String, Int)]
enumerate :: [SpecTree a] -> [Tree (ActionWith a) ((Int, Int), (Item a))]
enumerate tree = (mapM (traverse addPosition) tree >>= mapM (traverse addTotal)) `evalState` []
where
addPosition :: Item a -> EnumerateM (Int, Item a)
addPosition item = (,) <$> getOccurrence (itemRequirement item) <*> pure item
addTotal :: (Int, Item a) -> EnumerateM ((Int, Int), Item a)
addTotal (n, item) = do
total <- getTotal (itemRequirement item)
return ((n, total), item)
getTotal :: String -> EnumerateM Int
getTotal requirement = do
gets $ fromMaybe err . lookup requirement
where
err = error ("Test.Hspec.Discover.getTotal: No entry for requirement " ++ show requirement ++ "!")
getOccurrence :: String -> EnumerateM Int
getOccurrence requirement = do
xs <- get
let n = maybe 1 succ (lookup requirement xs)
put ((requirement, n) : filter ((/= requirement) . fst) xs)
return n
lookupLocation :: FilePath -> String -> Int -> Int -> String -> Maybe Location
lookupLocation file input n total requirement = loc
where
loc :: Maybe Location
loc = Location file <$> line <*> pure 0 <*> pure BestEffort
line :: Maybe Int
line = case occurrences of
xs | length xs == total -> Just (xs !! pred n)
_ -> Nothing
occurrences :: [Int]
occurrences = map fst (filter p inputLines)
where
p :: (Int, String) -> Bool
p = isInfixOf (show requirement) . snd
inputLines :: [(Int, String)]
inputLines = zip [1..] (lines input)