hpc-coveralls-0.2.0: src/Trace/Hpc/Coveralls.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module: Trace.Hpc.Coveralls
-- Copyright: (c) 2014 Guillaume Nargeot
-- License: BSD3
-- Maintainer: Guillaume Nargeot <guillaume+hackage@nargeot.com>
-- Stability: experimental
--
-- Types and functions for converting and sending hpc output to coveralls.io.
module Trace.Hpc.Coveralls ( generateCoverallsFromTix ) where
import Data.Aeson
import Data.Aeson.Types ()
import Data.List
import System.Exit (exitFailure)
import Text.Regex.Posix
import Trace.Hpc.Coveralls.Config
import Trace.Hpc.Lix
import Trace.Hpc.Mix
import Trace.Hpc.Tix
type CoverageData = (
String, -- source file path
String, -- file source code
Mix, -- module index data
TixModule) -- tixs recorded by hpc
-- single file coverage data in the format defined by coveralls.io
type SimpleCoverage = [CoverageEntry]
-- Is there a way to restrict this to only Number and Null?
type CoverageEntry = Value
hpcDir :: String
hpcDir = "dist/hpc/"
tixDir :: String
tixDir = hpcDir ++ "tix/"
mixDir :: String
mixDir = hpcDir ++ "mix/"
lixToSimpleCoverage :: Lix -> SimpleCoverage
lixToSimpleCoverage = map conv
where conv Full = Number 2
conv Partial = Number 1
conv None = Number 0
conv Irrelevant = Null
toSimpleCoverage :: Int -> [(MixEntry, Integer)] -> SimpleCoverage
toSimpleCoverage lineCount = lixToSimpleCoverage . toLix lineCount
coverageToJson :: CoverageData -> Value
coverageToJson (filePath, source, mix, tix) = object [
"name" .= filePath,
"source" .= source,
"coverage" .= coverage]
where coverage = toSimpleCoverage lineCount mixEntryTixs
lineCount = length $ lines source
mixEntryTixs = zip (getMixEntries mix) (tixModuleTixs tix)
getMixEntries (Mix _ _ _ _ mixEntries) = mixEntries
toCoverallsJson :: String -> String -> [CoverageData] -> Value
toCoverallsJson serviceName jobId coverageData = object [
"service_job_id" .= jobId,
"service_name" .= serviceName,
"source_files" .= map coverageToJson coverageData]
matchAny :: [String] -> String -> Bool
matchAny patterns fileName = any (fileName =~) $ map ("^" ++) patterns
readMix' :: String -> TixModule -> IO Mix
readMix' name tix = readMix [mixPath] (Right tix)
where mixPath = mixDir ++ dirName ++ "/"
dirName = case span (/= '/') modName of
(_, []) -> name
(packageId, _) -> packageId
TixModule modName _ _ _ = tix
-- | Create a list of coverage data from the tix input
toCoverageData :: String -- ^ test suite name
-> Tix -- ^ tix data
-> [String] -- ^ excluded source folders
-> IO [CoverageData] -- ^ coverage data list
toCoverageData testSuiteName (Tix tixs) excludeDirPatterns = do
mixs <- mapM (readMix' testSuiteName) tixs
let files = map filePath mixs
sources <- mapM readFile files
let coverageDataList = zip4 files sources mixs tixs
return $ filter sourceDirFilter coverageDataList
where filePath (Mix fp _ _ _ _) = fp
sourceDirFilter = not . matchAny excludeDirPatterns . fst4
fst4 (x, _, _, _) = x
-- | Generate coveralls json formatted code coverage from hpc coverage data
generateCoverallsFromTix :: String -- ^ CI name
-> String -- ^ CI Job ID
-> Config -- ^ hpc-coveralls configuration
-> IO Value -- ^ code coverage result in json format
generateCoverallsFromTix serviceName jobId config = do
mtix <- readTix tixPath
case mtix of
Nothing -> error ("Couldn't find the file " ++ tixPath) >> exitFailure
Just tixs -> do
coverageDatas <- toCoverageData testSuiteName tixs (excludedDirs config)
return $ toCoverallsJson serviceName jobId coverageDatas
where tixPath = tixDir ++ testSuiteName ++ "/" ++ getTixFileName testSuiteName
testSuiteName = head (testSuiteNames config) -- multiple test suite mode is supported at the moment