module HPCThreshold where import Text.Regex.PCRE.Heavy import Data.ByteString (ByteString) import Data.String.Interpolate -- | Data structure for a single threshold configuration. data Threshold = Threshold { thresholdName :: String , thresholdRegex :: ByteString , thresholdValue :: Double } deriving (Read, Show, Eq) type Coverage = Double -- | The result of evaluation. The _3 indicates whether the coverage passes the threshold or not. type ThresholdEvaluationResult = (Threshold, Coverage, Bool) -- | Extract the coverage from input string using a regex string extractCoverage :: String -- ^ The input string -> ByteString -- ^ The regex to extract the coverage. The regex should contain `(\\d+)` capture otherwise the coverage is always be 0. -> Coverage -- ^ The extracted coverage extractCoverage src regexStr = case compileM regexStr [] of Left e -> error $ "Unable to compile regex " ++ show regexStr ++ ": " ++ e Right regex -> case scan regex src of (_, coverage:_):_ -> read coverage _ -> 0 -- | Evaluate the given string against the given threshold, producing an evaluation result evaluate :: String -> Threshold -> ThresholdEvaluationResult evaluate src threshold = let coverage = extractCoverage src (thresholdRegex threshold) isPass = coverage >= thresholdValue threshold in (threshold, coverage, isPass) -- | Produce a human-friendly output of the evaluation result reportThreshold :: ThresholdEvaluationResult -> String reportThreshold (Threshold tName _ tValue, coverage, isPass) = let remark = if isPass then "✓" else "·" :: String sign = if isPass then "≥" else "<" :: String in [i|#{remark} #{tName}: #{coverage}% (#{sign} #{tValue}%)|] -- | Evaluate a string against a list of thresholds configuration and produce a report evaluateAndReport :: String -- ^ The input string -> [Threshold] -- ^ The list of thresholds -> (String, Bool) -- ^ Pair of report and whether there is any coverage under thresholds evaluateAndReport src thresholds = let evalResults = map (evaluate src) thresholds isAllThresholdPass = all (\(_, _, isPass) -> isPass) evalResults reportSummary = if isAllThresholdPass then "Code coverage threshold check: PASS" else "Code coverage threshold check: FAIL" report = unlines $ reportSummary : map reportThreshold evalResults in (report, isAllThresholdPass)