packages feed

stack-hpc-coveralls 0.0.3.0 → 0.0.4.0

raw patch · 9 files changed

+202/−76 lines, 9 filesdep +directorydep +unordered-containersdep +yamlPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: directory, unordered-containers, yaml

API changes (from Hackage documentation)

- SHC.Coverage: readMix' :: Config -> TixModule -> IO Mix
- SHC.Types: instance Data.Aeson.Types.Class.ToJSON SHC.Types.Commit
- SHC.Types: instance Data.Aeson.Types.Class.ToJSON SHC.Types.GitInfo
- SHC.Types: instance Data.Aeson.Types.Class.ToJSON SHC.Types.Remote
- SHC.Utils: checkStackVersion :: IO Bool
- SHC.Utils: getHpcDir :: String -> IO FilePath
- SHC.Utils: getMixDir :: IO FilePath
- SHC.Utils: stack :: [String] -> IO String
+ SHC.Stack: checkStackVersion :: IO Bool
+ SHC.Stack: getBaseMixDir :: IO FilePath
+ SHC.Stack: getHpcDir :: String -> IO FilePath
+ SHC.Stack: getProjectKey :: String -> IO String
+ SHC.Stack: getStackProjects :: IO [StackProject]
+ SHC.Stack: getStackQuery :: IO StackQuery
+ SHC.Stack: stack :: [String] -> IO String
+ SHC.Types: StackProject :: String -> Maybe FilePath -> String -> FilePath -> StackProject
+ SHC.Types: StackQuery :: [(String, FilePath)] -> StackQuery
+ SHC.Types: [fetchCoverage] :: Config -> Bool
+ SHC.Types: [packageName] :: Config -> String
+ SHC.Types: [stackProjectKey] :: StackProject -> String
+ SHC.Types: [stackProjectMixDir] :: StackProject -> FilePath
+ SHC.Types: [stackProjectName] :: StackProject -> String
+ SHC.Types: [stackProjectPath] :: StackProject -> Maybe FilePath
+ SHC.Types: [stackProjects] :: Config -> [StackProject]
+ SHC.Types: [stackQueryLocals] :: StackQuery -> [(String, FilePath)]
+ SHC.Types: data StackProject
+ SHC.Types: data StackQuery
+ SHC.Types: instance Data.Aeson.Types.FromJSON.FromJSON SHC.Types.StackQuery
+ SHC.Types: instance Data.Aeson.Types.ToJSON.ToJSON SHC.Types.Commit
+ SHC.Types: instance Data.Aeson.Types.ToJSON.ToJSON SHC.Types.GitInfo
+ SHC.Types: instance Data.Aeson.Types.ToJSON.ToJSON SHC.Types.Remote
+ SHC.Types: instance GHC.Show.Show SHC.Types.StackProject
+ SHC.Types: instance GHC.Show.Show SHC.Types.StackQuery
- SHC.Coverage: type ModuleCoverageData = (String, Mix, [Integer])
+ SHC.Coverage: type ModuleCoverageData = (ByteString, Mix, [Integer])
- SHC.Types: Config :: [String] -> String -> String -> Maybe String -> GitInfo -> FilePath -> FilePath -> ConversionType -> Config
+ SHC.Types: Config :: String -> [String] -> String -> String -> Maybe String -> GitInfo -> FilePath -> Maybe FilePath -> ConversionType -> [StackProject] -> Bool -> Config
- SHC.Types: [mixDir] :: Config -> FilePath
+ SHC.Types: [mixDir] :: Config -> Maybe FilePath

Files

USAGE.txt view
@@ -8,3 +8,9 @@     --repo-token=<token>  Coveralls repo token     --partial-coverage    allow partial line coverage     --dont-send           display Coveralls JSON instead of sending it+    --fetch-coverage      fetch and display Coveralls's overall coverage++Multiple packages:+     Use "combined" and "all" as package-name and suite-name resp. in+order to use all packages that stack has tested from a multiple+package repo.
app/Main.hs view
@@ -3,7 +3,7 @@ module Main     where -import           Control.Monad         (unless)+import           Control.Monad         (unless, when) import           Data.Aeson            (encode) import qualified Data.ByteString.Lazy  as BSL import           Data.List             (find)@@ -18,6 +18,7 @@  import           SHC.Api import           SHC.Coverage+import           SHC.Stack import           SHC.Types import           SHC.Utils @@ -56,16 +57,19 @@     unless (not $ null suites) $         putStrLn "Error: provide at least one test-suite name" >> exitFailure     (sn, jId) <- getServiceAndJobId-    Config <$> pure suites+    Config <$> pure pn+           <*> pure suites            <*> pure sn            <*> pure jId            <*> pure (args `getArg` longOption "repo-token")            <*> getGitInfo            <*> defaultOr args (longOption "hpc-dir") (getHpcDir pn)-           <*> defaultOr args (longOption "hpc-dir") getMixDir+           <*> pure (args `getArg` longOption "mix-dir")            <*> pure (if args `isPresent` longOption "partial-coverage"                         then PartialLines                         else FullLines)+           <*> getStackProjects+           <*> pure (args `isPresent` longOption "fetch-coverage")  main :: IO () main = do@@ -84,12 +88,13 @@              PostSuccess u -> do                  let apiUrl = u ++ ".json"                  putStrLn $ "Job URL: " ++ apiUrl-                 -- wait 5 seconds until the page is available-                 threadDelay $ 5 * 1000 * 1000-                 coverageResult <- readCoverageResult apiUrl-                 case coverageResult of-                     Just totalCov -> putStrLn $ "Coverage: " ++ show totalCov-                     Nothing       -> putStrLn "Failed to read total coverage"+                 when (fetchCoverage conf) $ do+                     -- wait 5 seconds until the page is available+                     threadDelay $ 5 * 1000 * 1000+                     coverageResult <- readCoverageResult apiUrl+                     case coverageResult of+                         Just totalCov -> putStrLn $ "Coverage: " ++ show totalCov+                         Nothing       -> putStrLn "Failed to read total coverage"              PostFailure msg -> do                  putStrLn $ "Error: " ++ msg                  exitFailure
src/SHC/Api.hs view
@@ -15,6 +15,7 @@     where  import           Codec.Binary.UTF8.String              (decode)+import           Control.Exception                     (catch) import           Data.Aeson                            (Value, encode) import           Data.Aeson.Lens                       (key, _Double, _String) import qualified Data.ByteString                       as BS@@ -38,21 +39,28 @@          -> Value         -- ^ The JSON object          -> IO PostResult sendData conf url json = do-    r <- post url [partFileRequestBody "json_file" fileName requestBody]+    r <- postWith httpOptions url [partFileRequestBody "json_file" fileName requestBody]     if r ^. responseStatus . statusCode == 200        then return $ readResponse r-       else return . PostFailure $-           "Error: " ++ decode (BS.unpack $ r ^. responseStatus . statusMessage)+       else return . PostFailure $ formatResponseError r     where fileName    = serviceName conf ++ "-" ++ jobId conf ++ ".json"           requestBody = RequestBodyLBS $ encode json+          httpOptions = defaults & checkStatus .~ Just noCheck+          noCheck _ _ _ = Nothing  readResponse :: Response LBS.ByteString -> PostResult readResponse r =     case r ^? responseBody . key "error" . _String of-      Just err -> PostFailure $ T.unpack err-      Nothing  -> case r ^? responseBody . key "url" . _String of-                    Just url -> PostSuccess $ T.unpack url-                    Nothing  -> PostFailure "Error: malformed response body"+      Just _  -> PostFailure $ formatResponseError r+      Nothing -> case r ^? responseBody . key "url" . _String of+                   Just url -> PostSuccess $ T.unpack url+                   Nothing  -> PostFailure "Error: malformed response body"++formatResponseError :: Response LBS.ByteString -> String+formatResponseError r =+    "Coveralls returned HTTP " ++ show (r ^. responseStatus . statusCode) +++    " " ++ decode (BS.unpack $ r ^. responseStatus . statusMessage) ++ "\n" +++    decode (LBS.unpack $ r ^. responseBody)  -- | Read the coverage results from Coveralls.io. readCoverageResult :: String -> IO (Maybe Double)
src/SHC/Coverage.hs view
@@ -19,23 +19,25 @@ #endif import           Data.Aeson import           Data.Aeson.Types           ()+import qualified Data.ByteString.Char8      as BS import qualified Data.ByteString.Lazy.Char8 as LBS import           Data.Digest.Pure.MD5 import           Data.Function import           Data.List+import           Data.Maybe                 (fromMaybe) import qualified Data.Map.Strict            as M import           SHC.Lix import           SHC.Types import           SHC.Utils import           System.Exit                (exitFailure)-import           System.FilePath            ((</>))+import           System.FilePath            ((</>), normalise) import           Trace.Hpc.Mix import           Trace.Hpc.Tix import           Trace.Hpc.Util -type ModuleCoverageData = ( String     -- file source code-                          , Mix        -- module index data-                          , [Integer]  -- tixs recorded by HPC+type ModuleCoverageData = ( BS.ByteString -- file source code+                          , Mix           -- module index data+                          , [Integer]     -- tixs recorded by HPC                           )  type TestSuiteCoverageData = M.Map FilePath ModuleCoverageData@@ -61,9 +63,6 @@     None       -> Number 0     Irrelevant -> Null -readMix' :: Config -> TixModule -> IO Mix-readMix' conf tix = readMix [SHC.Types.mixDir conf] (Right tix)- -- | Generate Coveralls JSON formatted code coverage from HPC coverage data generateCoverallsFromTix :: Config -> IO Value generateCoverallsFromTix conf = do@@ -84,13 +83,18 @@         Nothing -> putStrLn ("Couldn't find the file " ++ tixPath) >>                    exitFailure         Just (Tix tixs) -> do-            mixs <- mapM (readMix' conf) tixs-            let files = map filePath mixs-            sources <- mapM readFile files-            let coverageData = zip4 files sources mixs (map tixModuleTixs tixs)+            coverageData <- mapM getCoverageData tixs             let filteredCoverageData = filter sourceDirFilter coverageData             return $ M.fromList $ map toFirstAndRest filteredCoverageData-            where filePath (Mix fp _ _ _ _) = fp+            where getCoverageData tixModule@(TixModule modName _ _ tixs) = do+                    let pkgKey = takeWhile (/= '/') modName+                        stackProj =+                          fromMaybe (error $ "readCoverageData/filePath/stackProj: couldn't find " ++ pkgKey) $+                          find ((== pkgKey) . stackProjectKey) (stackProjects conf)+                    mix@(Mix origFp _ _ _ _) <- readMix [fromMaybe (stackProjectMixDir stackProj) (mixDir conf)] (Right tixModule)+                    let fp = normalise $ maybe id (</>) (stackProjectPath stackProj) origFp+                    source <- BS.readFile fp+                    return (fp, source, mix, tixs)                   sourceDirFilter = not . matchAny excludeDirPatterns . fst4                   excludeDirPatterns = []  -- XXX: for now @@ -110,15 +114,16 @@ coverageToJson :: LixConverter -> FilePath -> ModuleCoverageData -> Value coverageToJson converter path (source, mix, tixs) =     object [ "name"          .= path-           , "source_digest" .= (show . md5 . LBS.pack) source+           , "source_digest" .= (show . md5 . LBS.fromStrict) source            , "coverage"      .= coverage            ]     where coverage = toSimpleCoverage converter lineCount mixEntriesTixs-          lineCount = length $ lines source+          sourceLines = BS.lines source+          lineCount = length sourceLines           mixEntriesTixs = groupMixEntryTixs mixEntryTixs           mixEntryTixs = zip3 mixEntries tixs (map getExprSource' mixEntries)           Mix _ _ _ _ mixEntries = mix-          getExprSource' = getExprSource $ lines source+          getExprSource' = getExprSource $ map BS.unpack sourceLines  mergeCoverageData :: [TestSuiteCoverageData] -> TestSuiteCoverageData mergeCoverageData = foldr1 $ M.unionWith mergeModule
+ src/SHC/Stack.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE CPP #-}+-- |+-- Module:      SHC.Stack+-- Copyright:   (c) 2014-2015 Guillaume Nargeot, (c) 2015-2016 Felipe Lessa+-- License:     BSD3+-- Maintainer:  Michele Lacchia <michelelacchia@gmail.com>+-- Stability:   experimental+--+-- Utility functions related to Stack.++module SHC.Stack+    where++import Data.Version+#if __GLASGOW_HASKELL__ < 710+import Control.Applicative ((<$>), (<*>))+#endif+import Control.Monad (forM, guard)+import System.Directory (makeRelativeToCurrentDirectory)+import System.FilePath ((</>), equalFilePath)++import qualified Data.ByteString.Char8 as BS8+import qualified Data.Yaml as Y++import SHC.Types+import SHC.Utils+++stack :: [String] -> IO String+stack = readP "stack"++-- | Verify that the required Stack is present.+checkStackVersion :: IO Bool+checkStackVersion = do+    let lowerBound = Version [0,1,7,0] []+    stackVersion <- stack ["--numeric-version"]+    return $ verifyVersion stackVersion lowerBound++-- | Return the HPC data directory, given the package name.+getHpcDir :: String -> IO FilePath+getHpcDir package = (</> package) <$> stack ["path", "--local-hpc-root"]++-- | Return the HPC mix directory, where module data is stored.  This+-- path needs to be prefixed with the project's path+-- (cf. 'stackProjectPath').+getBaseMixDir :: IO FilePath+getBaseMixDir = (</> "hpc") <$> stack ["path", "--dist-dir"]++-- | Get relevant information from @stack query@.  Used to find+-- package filepaths.+getStackQuery :: IO StackQuery+getStackQuery = (Y.decodeEither' . BS8.pack <$> stack ["query"]) >>= either err return+  where err = fail . (++) "getStackQuery: Couldn't decode the result of 'stack query' as YAML: " . show++-- | Get the key that GHC uses for the given package.+getProjectKey :: String -> IO String+getProjectKey pkgName = stack ["exec", "--", "ghc-pkg", "field", pkgName, "key", "--simple-output"]++-- | Get the Stack info needed to find project files.+getStackProjects :: IO [StackProject]+getStackProjects = do+  sq <- getStackQuery+  baseMixDir <- getBaseMixDir+  forM (stackQueryLocals sq) $ \(pkgName, filepath) -> do+    relfp <- makeRelativeToCurrentDirectory filepath+    let mpath = guard (not $ relfp `equalFilePath` ".") >> Just relfp+    key <- getProjectKey pkgName+    return+      StackProject+        { stackProjectName   = pkgName+        , stackProjectPath   = mpath+        , stackProjectKey    = key+        , stackProjectMixDir = maybe id (</>) mpath baseMixDir+        }
src/SHC/Types.hs view
@@ -1,12 +1,19 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP, OverloadedStrings #-}  module SHC.Types     where +import           Control.Monad (forM) import           Data.Aeson+import qualified Data.HashMap.Strict as HM+import qualified Data.Text           as T import           Trace.Hpc.Mix +#if __GLASGOW_HASKELL__ < 710+import Control.Applicative ((<$>))+#endif + data ConversionType = FullLines                     | PartialLines                     deriving (Show, Eq)@@ -30,14 +37,17 @@                 deriving (Show)  data Config = Config-    { suitesName  :: [String]-    , serviceName :: String-    , jobId       :: String-    , repoToken   :: Maybe String-    , gitInfo     :: GitInfo-    , hpcDir      :: FilePath-    , mixDir      :: FilePath-    , conversion  :: ConversionType+    { packageName   :: String+    , suitesName    :: [String]+    , serviceName   :: String+    , jobId         :: String+    , repoToken     :: Maybe String+    , gitInfo       :: GitInfo+    , hpcDir        :: FilePath+    , mixDir        :: Maybe FilePath+    , conversion    :: ConversionType+    , stackProjects :: [StackProject]+    , fetchCoverage :: Bool     }  data GitInfo = GitInfo@@ -79,3 +89,36 @@     toJSON r = object [ "name" .= name r                       , "url"  .= url r                       ]++-- | Data returned by the @stack query@ command.+data StackQuery =+  StackQuery+  { stackQueryLocals :: [(String, FilePath)]+    -- ^ A list of pairs of @(package-name, filepath)@, where the+    -- @filepath@ is the absolute 'FilePath' where @package-name@ is+    -- located.+  } deriving (Show)++instance FromJSON StackQuery where+  parseJSON = withObject "StackQuery" $ \o -> StackQuery <$> (o .: "locals" >>= parseLocals)+    where+      parseLocals =+        withObject "StackQuery/locals" $ \o ->+          forM (HM.toList o) $ \(pkgName, val) -> do+            filepath <- withObject "StackQuery/locals/package" (.: "path") val+            return (T.unpack pkgName, filepath)+++-- | Information we've collected about a stack project.+data StackProject =+  StackProject+  { stackProjectName   :: String+    -- ^ The name of this project.+  , stackProjectPath   :: Maybe FilePath+    -- ^ Path of the project relative to current path.  @Nothing@ iff+    -- the project's path is the current path.+  , stackProjectKey    :: String+    -- ^ Key that @ghc-pkg@ uses for this project.+  , stackProjectMixDir :: FilePath+    -- ^ The project's mix dir (cf. 'getMixDir').+  } deriving (Show)
src/SHC/Utils.hs view
@@ -19,7 +19,6 @@ import           Control.Applicative ((<$>), (<*>)) #endif import           Text.ParserCombinators.ReadP-import           System.FilePath     ((</>)) import           System.Process      (readProcess)  import           SHC.Types@@ -31,9 +30,6 @@ git :: [String] -> IO String git = readP "git" -stack :: [String] -> IO String-stack = readP "stack"- -- | Get information about the Git repo in the current directory. getGitInfo :: IO GitInfo getGitInfo = GitInfo <$> headRef <*> branchName <*> getRemotes@@ -46,7 +42,7 @@           branchName = git ["rev-parse", "--abbrev-ref", "HEAD"]  getRemotes :: IO [Remote]-getRemotes = nubBy ((==) `on` name) <$> parseRemotes <$> git ["remote", "-v"]+getRemotes = nubBy ((==) `on` name) . parseRemotes <$> git ["remote", "-v"]     where parseRemotes :: String -> [Remote]           parseRemotes input = do             line <- lines input@@ -54,27 +50,12 @@             guard $ length fields >= 2             return $ Remote (head fields) (fields !! 1) --- | Verify that the required Stack is present.-checkStackVersion :: IO Bool-checkStackVersion = do-    let lowerBound = Version [0,1,7,0] []-    stackVersion <- stack ["--numeric-version"]-    return $ verifyVersion stackVersion lowerBound- -- | Check whether a string is a version and if it is -- greater than or equal to a specified version. verifyVersion :: String -> Version -> Bool verifyVersion ver lowerBound =         let parses = readP_to_S parseVersion ver         in  not (null parses) && (lowerBound <= fst (last parses))---- | Return the HPC data directory, given the package name.-getHpcDir :: String -> IO FilePath-getHpcDir package = (</> package) <$> stack ["path", "--local-hpc-root"]---- | Return the HPC mix directory, where module data is stored.-getMixDir :: IO FilePath-getMixDir = (</> "hpc") <$> stack ["path", "--dist-dir"]  fst3 :: (a, b, c) -> a fst3 (x, _, _) = x
stack-hpc-coveralls.cabal view
@@ -1,5 +1,5 @@ name:                stack-hpc-coveralls-version:             0.0.3.0+version:             0.0.4.0 synopsis:            Initial project template from stack description:         Please see README.md homepage:            http://github.com/rubik/stack-hpc-coveralls@@ -22,20 +22,24 @@                        SHC.Utils                        SHC.Lix                        SHC.Api-  build-depends:       base           >=4.7  && <5-                     , hpc            >=0.6-                     , filepath       >=1.3-                     , process        >=1.2-                     , pureMD5        >=2.1-                     , containers     >=0.5-                     , aeson          >=0.8-                     , bytestring     >=0.10-                     , utf8-string    >=1-                     , text           >=1.2-                     , wreq           >=0.3-                     , http-client    >=0.4-                     , lens           >=4.7-                     , lens-aeson     >=1.0+                       SHC.Stack+  build-depends:       base                 >=4.7  && <5+                     , hpc                  >=0.6+                     , directory            >=1.0+                     , filepath             >=1.3+                     , process              >=1.2+                     , pureMD5              >=2.1+                     , containers           >=0.5+                     , aeson                >=0.8+                     , bytestring           >=0.10+                     , unordered-containers >=0.2+                     , utf8-string          >=1+                     , text                 >=1.2+                     , wreq                 >=0.3+                     , http-client          >=0.4+                     , lens                 >=4.7+                     , lens-aeson           >=1.0+                     , yaml                 >=0.8   default-language:    Haskell2010  executable shc
stack.yaml view
@@ -1,7 +1,7 @@ # For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md  # Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)-resolver: lts-3.11+resolver: nightly-2016-06-15  # Local packages, usually specified by relative directory name packages: