tasty-discover-1.0.0: src/Test/Tasty/Util.hs
-- | Utility functions.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Test.Tasty.Util (
importList
, findTests
, getListOfTests
-- Testing purposes
, fileToTest
, getFilesRecursive
, isValidModuleChar
, isValidModuleName
) where
import Control.Applicative ((<|>))
import Control.Monad (filterM)
import Data.Char (isAlphaNum, isUpper)
import Data.List (intercalate, sort, stripPrefix)
import Data.Maybe (mapMaybe)
import Data.String (IsString, fromString)
import System.Directory (doesDirectoryExist, doesFileExist, getDirectoryContents)
import System.FilePath (splitDirectories, splitFileName, (</>))
import System.FilePath.Posix (splitExtension)
import Test.Tasty.TH (extractTestFunctions)
import Test.Tasty.Config (Config(..))
import Test.Tasty.Type
instance IsString ShowS where
fromString = showString
-- | Import statements for a list of tests.
importList :: [Test] -> Config -> ShowS
importList ts config =
foldr ((.) . f) "" ts
where
f :: Test -> ShowS
f test = if noModuleSuffix config then
"import " . showString (testModule test) . "\n"
else
case configModuleSuffix config of
Just suffix' -> "import " . showString (testModule test) . showString (suffix' ++ "\n")
_ -> "import " . showString (testModule test) . "Test\n"
-- | Is 'c' a valid character in a Haskell module name?
isValidModuleChar :: Char -> Bool
isValidModuleChar c = isAlphaNum c || c == '_' || c == '\''
-- | Is 'cs' a valid Haskell module name?
isValidModuleName :: String -> Bool
isValidModuleName [] = False
isValidModuleName (c:cs) = isUpper c && all isValidModuleChar cs
-- | All files under 'baseDir'.
getFilesRecursive :: FilePath -> IO [FilePath]
getFilesRecursive baseDir = sort <$> go []
where
go :: FilePath -> IO [FilePath]
go dir = do
c <- map (dir </>) . filter (`notElem` [".", ".."]) <$> getDirectoryContents (baseDir </> dir)
dirs <- filterM (doesDirectoryExist . (baseDir </>)) c >>= mapM go
files <- filterM (doesFileExist . (baseDir </>)) c
return (files ++ concat dirs)
-- | Convert a file to a File type.
fileToTest :: FilePath -> Config -> FilePath -> Maybe Test
fileToTest dir conf file =
let
suffix :: Maybe String
suffix = configModuleSuffix conf
noModule :: Bool
noModule = noModuleSuffix conf
files :: [FilePath]
files = reverse $ splitDirectories file
in
if noModule then catchAll files else case suffix of
Just suffix' -> filterBySuffix suffix' files
Nothing -> filterBySuffix "Test" files
where
filterBySuffix :: String -> [FilePath] -> Maybe Test
filterBySuffix suffix files =
case files of
x:xs -> case
stripSuffix (suffix ++ ".hs") x <|> stripSuffix (suffix ++ ".lhs") x of
Just name | isValidModuleName name && all isValidModuleName xs ->
Just . Test (dir </> file) $
(intercalate "." . reverse) (name : xs)
_ -> Nothing
_ -> Nothing
stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]
stripSuffix suff str = reverse <$> stripPrefix (reverse suff) (reverse str)
catchAll :: [FilePath] -> Maybe Test
catchAll (x:xs) =
let name = fst $ splitExtension x in
if isValidModuleName name && all isValidModuleName xs then
Just . Test (dir </> file) $ (intercalate "." . reverse) (name : xs)
else Nothing
catchAll _ = Nothing
-- | All test modules under 'dir'.
findTests :: FilePath -> Config -> IO [Test]
findTests path config =
let (dir, file) = splitFileName path
tests = mapMaybe $ fileToTest dir config
in
tests . filter (/= file) <$> getFilesRecursive dir
-- | All test function names in 'src'.
getListOfTests :: FilePath -> Config -> IO [String]
getListOfTests src conf = do
allFiles <- fmap testFile <$> findTests src conf
allTests <- mapM extractTestFunctions allFiles
return $ concat allTests