Skip to content

Commit 2cd3e6f

Browse files
committed
Per-component multi-package builds with coverage enabled
This commits re-enables per-component builds when coverage checking is enabled. This restriction was previously added in #5004 to fix #4798. - #4798 was subsequently fixed "again" with the fix for #5213, in #7493 by fixing the paths of the testsuite `.mix` files to the same location as that of the main library component. Therefore the restriction to treat testsuites per-package (legacy-fallback) is no longer needed. We went further and fixed coverage for internal sublibraries, packages with backpack (but without generating coverage information for indefinite and instantiated units -- it is not clear what it would mean for HPC to support this), and coverage for multi-package projects. 1. We allow hpc in per-component builds 2. To generate hpc files in the appropriate component directories in the distribution tree, we remove the hack from #7493 and instead determine the `.mix` directories that are included in the call to `hpc markup` by passing the list of libraries in the project from the cabal-install invocation of test. We also drop an unnecessary directory in the hpc file hierarchy. 3. To account for internal (non-backpack) libraries, we include the mix dirs and modules of all (non-indefinite and non-instantiations) libraries in the project Indefinite libraries and instantiations are ignored as it is not obvious what it means for HPC to support backpack, e.g. covering a library function that two different instantiations 4. We now only reject coverage if there are no libraries at all in the project, rather than if there are no libraries in the package. This allows us to drop the coverage masking logic in cabal.project.coverage while still having coverage of cabal-install (i.e. cabal test --enable-coverage cabal-install now works without the workaround) Even though we allow multi-package project coverage, we still cover each package independently -- the tix files resulting from all packages are not combined for the time being. Includes tests for #6440, #6397, #8609, and #4798 (the test for #5213 already exists) Fixes #6440 (internal libs coverage), #6397 (backpack breaks coverage) , #8609 (multi-package coverage report) and fixes in a new way the previously fixed #4798, #5213.
1 parent dc1b44b commit 2cd3e6f

File tree

32 files changed

+225
-185
lines changed

32 files changed

+225
-185
lines changed

Cabal/src/Distribution/Simple/Errors.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -715,12 +715,12 @@ exceptionMessage e = case e of
715715
"Could not find test program \""
716716
++ cmd
717717
++ "\". Did you build the package first?"
718-
TestCoverageSupport -> "Test coverage is only supported for packages with a library component."
718+
TestCoverageSupport -> "Test coverage is only supported for projects with at least one (non-backpack) library component."
719719
Couldn'tFindTestProgLibV09 cmd ->
720720
"Could not find test program \""
721721
++ cmd
722722
++ "\". Did you build the package first?"
723-
TestCoverageSupportLibV09 -> "Test coverage is only supported for packages with a library component."
723+
TestCoverageSupportLibV09 -> "Test coverage is only supported for projects with at least one (non-backpack) library component."
724724
RawSystemStdout errors -> errors
725725
FindFileCwd fileName -> fileName ++ " doesn't exist"
726726
FindFileEx fileName -> fileName ++ " doesn't exist"

Cabal/src/Distribution/Simple/GHC.hs

+2-7
Original file line numberDiff line numberDiff line change
@@ -643,15 +643,10 @@ buildOrReplLib mReplFlags verbosity numJobs pkg_descr lbi lib clbi = do
643643
-- Determine if program coverage should be enabled and if so, what
644644
-- '-hpcdir' should be.
645645
let isCoverageEnabled = libCoverage lbi
646-
-- TODO: Historically HPC files have been put into a directory which
647-
-- has the package name. I'm going to avoid changing this for
648-
-- now, but it would probably be better for this to be the
649-
-- component ID instead...
650-
pkg_name = prettyShow (PD.package pkg_descr)
651646
distPref = fromFlag $ configDistPref $ configFlags lbi
652647
hpcdir way
653648
| forRepl = mempty -- HPC is not supported in ghci
654-
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way pkg_name
649+
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way
655650
| otherwise = mempty
656651

657652
createDirectoryIfMissingVerbose verbosity True libTargetDir
@@ -1548,7 +1543,7 @@ gbuild verbosity numJobs pkg_descr lbi bm clbi = do
15481543
distPref = fromFlag $ configDistPref $ configFlags lbi
15491544
hpcdir way
15501545
| gbuildIsRepl bm = mempty -- HPC is not supported in ghci
1551-
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way (gbuildName bm)
1546+
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way
15521547
| otherwise = mempty
15531548

15541549
rpaths <- getRPaths lbi clbi

Cabal/src/Distribution/Simple/GHCJS.hs

+3-8
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ buildOrReplLib
481481
-> Library
482482
-> ComponentLocalBuildInfo
483483
-> IO ()
484-
buildOrReplLib mReplFlags verbosity numJobs pkg_descr lbi lib clbi = do
484+
buildOrReplLib mReplFlags verbosity numJobs _pkg_descr lbi lib clbi = do
485485
let uid = componentUnitId clbi
486486
libTargetDir = componentBuildDir lbi clbi
487487
whenVanillaLib forceVanilla =
@@ -515,15 +515,10 @@ buildOrReplLib mReplFlags verbosity numJobs pkg_descr lbi lib clbi = do
515515
-- Determine if program coverage should be enabled and if so, what
516516
-- '-hpcdir' should be.
517517
let isCoverageEnabled = libCoverage lbi
518-
-- TODO: Historically HPC files have been put into a directory which
519-
-- has the package name. I'm going to avoid changing this for
520-
-- now, but it would probably be better for this to be the
521-
-- component ID instead...
522-
pkg_name = prettyShow (PD.package pkg_descr)
523518
distPref = fromFlag $ configDistPref $ configFlags lbi
524519
hpcdir way
525520
| forRepl = mempty -- HPC is not supported in ghci
526-
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way pkg_name
521+
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way
527522
| otherwise = mempty
528523

529524
createDirectoryIfMissingVerbose verbosity True libTargetDir
@@ -1243,7 +1238,7 @@ gbuild verbosity numJobs pkg_descr lbi bm clbi = do
12431238
distPref = fromFlag $ configDistPref $ configFlags lbi
12441239
hpcdir way
12451240
| gbuildIsRepl bm = mempty -- HPC is not supported in ghci
1246-
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way (gbuildName bm)
1241+
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way
12471242
| otherwise = mempty
12481243

12491244
rpaths <- getRPaths lbi clbi

Cabal/src/Distribution/Simple/Hpc.hs

+46-90
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE FlexibleContexts #-}
22
{-# LANGUAGE RankNTypes #-}
3+
{-# LANGUAGE NamedFieldPuns #-}
34

45
-----------------------------------------------------------------------------
56

@@ -22,16 +23,14 @@ module Distribution.Simple.Hpc
2223
, tixDir
2324
, tixFilePath
2425
, markupPackage
25-
, markupTest
2626
) where
2727

2828
import Distribution.Compat.Prelude
2929
import Prelude ()
3030

3131
import Distribution.ModuleName (main)
3232
import Distribution.PackageDescription
33-
( Library (..)
34-
, TestSuite (..)
33+
( TestSuite (..)
3534
, testModules
3635
)
3736
import qualified Distribution.PackageDescription as PD
@@ -48,6 +47,8 @@ import Distribution.Verbosity (Verbosity ())
4847
import Distribution.Version (anyVersion)
4948
import System.Directory (createDirectoryIfMissing, doesFileExist)
5049
import System.FilePath
50+
import Distribution.Simple.Setup (TestFlags(..))
51+
import Distribution.Simple.Flag (fromFlagOrDefault)
5152

5253
-- -------------------------------------------------------------------------
5354
-- Haskell Program Coverage
@@ -73,44 +74,16 @@ mixDir
7374
-- ^ \"dist/\" prefix
7475
-> Way
7576
-> FilePath
76-
-- ^ Component name
77-
-> FilePath
7877
-- ^ Directory containing test suite's .mix files
79-
mixDir distPref way name = hpcDir distPrefBuild way </> "mix" </> name
80-
where
81-
-- This is a hack for HPC over test suites, needed to match the directory
82-
-- where HPC saves and reads .mix files when the main library of the same
83-
-- package is being processed, perhaps in a previous cabal run (#5213).
84-
-- E.g., @distPref@ may be
85-
-- @./dist-newstyle/build/x86_64-linux/ghc-9.0.1/cabal-gh5213-0.1/t/tests@
86-
-- but the path where library mix files reside has two less components
87-
-- at the end (@t/tests@) and this reduced path needs to be passed to
88-
-- both @hpc@ and @ghc@. For non-default optimization levels, the path
89-
-- suffix is one element longer and the extra path element needs
90-
-- to be preserved.
91-
distPrefElements = splitDirectories distPref
92-
distPrefBuild = case drop (length distPrefElements - 3) distPrefElements of
93-
["t", _, "noopt"] ->
94-
joinPath $
95-
take (length distPrefElements - 3) distPrefElements
96-
++ ["noopt"]
97-
["t", _, "opt"] ->
98-
joinPath $
99-
take (length distPrefElements - 3) distPrefElements
100-
++ ["opt"]
101-
[_, "t", _] ->
102-
joinPath $ take (length distPrefElements - 2) distPrefElements
103-
_ -> distPref
78+
mixDir distPref way = hpcDir distPref way </> "mix"
10479

10580
tixDir
10681
:: FilePath
10782
-- ^ \"dist/\" prefix
10883
-> Way
10984
-> FilePath
110-
-- ^ Component name
111-
-> FilePath
11285
-- ^ Directory containing test suite's .tix files
113-
tixDir distPref way name = hpcDir distPref way </> "tix" </> name
86+
tixDir distPref way = hpcDir distPref way </> "tix"
11487

11588
-- | Path to the .tix file containing a test suite's sum statistics.
11689
tixFilePath
@@ -121,17 +94,15 @@ tixFilePath
12194
-- ^ Component name
12295
-> FilePath
12396
-- ^ Path to test suite's .tix file
124-
tixFilePath distPref way name = tixDir distPref way name </> name <.> "tix"
97+
tixFilePath distPref way name = tixDir distPref way </> name <.> "tix"
12598

12699
htmlDir
127100
:: FilePath
128101
-- ^ \"dist/\" prefix
129102
-> Way
130103
-> FilePath
131-
-- ^ Component name
132-
-> FilePath
133104
-- ^ Path to test suite's HTML markup directory
134-
htmlDir distPref way name = hpcDir distPref way </> "html" </> name
105+
htmlDir distPref way = hpcDir distPref way </> "html"
135106

136107
-- | Attempt to guess the way the test suites in this package were compiled
137108
-- and linked with the library so the correct module interfaces are found.
@@ -141,58 +112,22 @@ guessWay lbi
141112
| withDynExe lbi = Dyn
142113
| otherwise = Vanilla
143114

144-
-- | Generate the HTML markup for a test suite.
145-
markupTest
146-
:: Verbosity
147-
-> LocalBuildInfo
148-
-> FilePath
149-
-- ^ \"dist/\" prefix
150-
-> String
151-
-- ^ Library name
152-
-> TestSuite
153-
-> Library
154-
-> IO ()
155-
markupTest verbosity lbi distPref libraryName suite library = do
156-
tixFileExists <- doesFileExist $ tixFilePath distPref way $ testName'
157-
when tixFileExists $ do
158-
-- behaviour of 'markup' depends on version, so we need *a* version
159-
-- but no particular one
160-
(hpc, hpcVer, _) <-
161-
requireProgramVersion
162-
verbosity
163-
hpcProgram
164-
anyVersion
165-
(withPrograms lbi)
166-
let htmlDir_ = htmlDir distPref way testName'
167-
markup
168-
hpc
169-
hpcVer
170-
verbosity
171-
(tixFilePath distPref way testName')
172-
mixDirs
173-
htmlDir_
174-
(exposedModules library)
175-
notice verbosity $
176-
"Test coverage report written to "
177-
++ htmlDir_
178-
</> "hpc_index" <.> "html"
179-
where
180-
way = guessWay lbi
181-
testName' = unUnqualComponentName $ testName suite
182-
mixDirs = map (mixDir distPref way) [testName', libraryName]
183-
184-
-- | Generate the HTML markup for all of a package's test suites.
115+
-- | Generate the HTML markup for a package's test suites.
185116
markupPackage
186117
:: Verbosity
118+
-> TestFlags
187119
-> LocalBuildInfo
188120
-> FilePath
189-
-- ^ \"dist/\" prefix
121+
-- ^ Testsuite \"dist/\" prefix
190122
-> PD.PackageDescription
191123
-> [TestSuite]
192124
-> IO ()
193-
markupPackage verbosity lbi distPref pkg_descr suites = do
194-
let tixFiles = map (tixFilePath distPref way) testNames
125+
markupPackage verbosity TestFlags{testCoverageLibsDistPref, testCoverageLibsModules} lbi testDistPref pkg_descr suites = do
126+
let tixFiles = map (tixFilePath testDistPref way) testNames
195127
tixFilesExist <- traverse doesFileExist tixFiles
128+
putStrLn "ROMES: WRITING"
129+
print testCoverageLibsDistPref
130+
print testCoverageLibsModules
196131
when (and tixFilesExist) $ do
197132
-- behaviour of 'markup' depends on version, so we need *a* version
198133
-- but no particular one
@@ -202,19 +137,40 @@ markupPackage verbosity lbi distPref pkg_descr suites = do
202137
hpcProgram
203138
anyVersion
204139
(withPrograms lbi)
205-
let outFile = tixFilePath distPref way libraryName
206-
htmlDir' = htmlDir distPref way libraryName
207-
excluded = concatMap testModules suites ++ [main]
208-
createDirectoryIfMissing True $ takeDirectory outFile
209-
union hpc verbosity tixFiles outFile excluded
210-
markup hpc hpcVer verbosity outFile mixDirs htmlDir' included
140+
let htmlDir' = htmlDir testDistPref way
141+
-- The tix file used to generate the report is either the testsuite's
142+
-- tix file, when there is only one testsuite, or the sum of the tix
143+
-- files of all testsuites in the package, which gets put under pkgName
144+
-- for this component (a bit weird)
145+
-- TODO: cabal-install should pass to Cabal where to put the summed tix
146+
-- and report, and perhaps even the testsuites from other packages in
147+
-- the project which are currently not accounted for in the summed
148+
-- report.
149+
tixFile <- case suites of
150+
-- We call 'markupPackage' once for each testsuite to run individually,
151+
-- to get the coverage report of just the one testsuite
152+
[oneTest] -> do
153+
let testName' = unUnqualComponentName $ testName oneTest
154+
return $
155+
tixFilePath testDistPref way testName'
156+
-- And call 'markupPackage' once per `test` invocation with all the
157+
-- testsuites to run, which results in multiple tix files being considered
158+
_ -> do
159+
let excluded = concatMap testModules suites ++ [main]
160+
pkgName = prettyShow $ PD.package pkg_descr
161+
summedTixFile = tixFilePath testDistPref way pkgName
162+
createDirectoryIfMissing True $ takeDirectory summedTixFile
163+
union hpc verbosity tixFiles summedTixFile excluded
164+
return summedTixFile
165+
166+
markup hpc hpcVer verbosity tixFile mixDirs htmlDir' included
211167
notice verbosity $
212168
"Package coverage report written to "
213169
++ htmlDir'
214170
</> "hpc_index.html"
215171
where
216172
way = guessWay lbi
217173
testNames = fmap (unUnqualComponentName . testName) suites
218-
mixDirs = map (mixDir distPref way) $ libraryName : testNames
219-
included = concatMap (exposedModules) $ PD.allLibraries pkg_descr
220-
libraryName = prettyShow $ PD.package pkg_descr
174+
mixDirs = mixDir testDistPref way : map (`mixDir` way) (fromFlagOrDefault [] testCoverageLibsDistPref)
175+
included = fromFlagOrDefault [] testCoverageLibsModules
176+

Cabal/src/Distribution/Simple/Setup/Test.hs

+13
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import Distribution.Verbosity
4141
import qualified Text.PrettyPrint as Disp
4242

4343
import Distribution.Simple.Setup.Common
44+
import Distribution.ModuleName (ModuleName)
4445

4546
-- ------------------------------------------------------------
4647

@@ -88,6 +89,16 @@ data TestFlags = TestFlags
8889
, testKeepTix :: Flag Bool
8990
, testWrapper :: Flag FilePath
9091
, testFailWhenNoTestSuites :: Flag Bool
92+
, testCoverageLibsModules :: Flag [ModuleName]
93+
-- ^ The list of all modules from libraries in the local project that should
94+
-- be included in the hpc coverage report.
95+
, testCoverageLibsDistPref :: Flag [FilePath]
96+
-- ^ The path to each main or sub library local to this project to include in
97+
-- coverage reporting (notably, this excludes indefinite libraries and
98+
-- instantiations because HPC does not support backpack - Nov. 2023). Cabal
99+
-- uses these paths as dist prefixes to determine the path to the `mix` dirs
100+
-- of each library to cover.
101+
91102
, -- TODO: think about if/how options are passed to test exes
92103
testOptions :: [PathTemplate]
93104
}
@@ -104,6 +115,8 @@ defaultTestFlags =
104115
, testKeepTix = toFlag False
105116
, testWrapper = NoFlag
106117
, testFailWhenNoTestSuites = toFlag False
118+
, testCoverageLibsModules = NoFlag
119+
, testCoverageLibsDistPref = NoFlag
107120
, testOptions = []
108121
}
109122

Cabal/src/Distribution/Simple/Test.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ test args pkg_descr lbi flags = do
133133
writeFile packageLogFile $ show packageLog
134134

135135
when (LBI.testCoverage lbi) $
136-
markupPackage verbosity lbi distPref pkg_descr $
136+
markupPackage verbosity flags lbi distPref pkg_descr $
137137
map (fst . fst) testsToRun
138138

139139
unless allOk exitFailure

Cabal/src/Distribution/Simple/Test/ExeV10.hs

+4-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import Prelude ()
1010

1111
import Distribution.Compat.Environment
1212
import qualified Distribution.PackageDescription as PD
13-
import Distribution.Pretty
1413
import Distribution.Simple.Build.PathsModule
1514
import Distribution.Simple.BuildPaths
1615
import Distribution.Simple.Compiler
@@ -51,7 +50,7 @@ runTest
5150
runTest pkg_descr lbi clbi flags suite = do
5251
let isCoverageEnabled = LBI.testCoverage lbi
5352
way = guessWay lbi
54-
tixDir_ = tixDir distPref way testName'
53+
tixDir_ = tixDir distPref way
5554

5655
pwd <- getCurrentDirectory
5756
existingEnv <- getEnvironment
@@ -171,11 +170,9 @@ runTest pkg_descr lbi clbi flags suite = do
171170
notice verbosity $ summarizeSuiteFinish suiteLog
172171

173172
when isCoverageEnabled $
174-
case PD.library pkg_descr of
175-
Nothing ->
176-
dieWithException verbosity TestCoverageSupport
177-
Just library ->
178-
markupTest verbosity lbi distPref (prettyShow $ PD.package pkg_descr) suite library
173+
if null $ testCoverageLibsDistPref flags
174+
then dieWithException verbosity TestCoverageSupport
175+
else markupPackage verbosity flags lbi distPref pkg_descr [suite]
179176

180177
return suiteLog
181178
where

Cabal/src/Distribution/Simple/Test/LibV09.hs

+5-7
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ runTest pkg_descr lbi clbi flags suite = do
8080

8181
-- Remove old .tix files if appropriate.
8282
unless (fromFlag $ testKeepTix flags) $ do
83-
let tDir = tixDir distPref way testName'
83+
let tDir = tixDir distPref way
8484
exists' <- doesDirectoryExist tDir
8585
when exists' $ removeDirectoryRecursive tDir
8686

8787
-- Create directory for HPC files.
88-
createDirectoryIfMissing True $ tixDir distPref way testName'
88+
createDirectoryIfMissing True $ tixDir distPref way
8989

9090
-- Write summary notices indicating start of test suite
9191
notice verbosity $ summarizeSuiteStart testName'
@@ -186,11 +186,9 @@ runTest pkg_descr lbi clbi flags suite = do
186186
notice verbosity $ summarizeSuiteFinish suiteLog
187187

188188
when isCoverageEnabled $
189-
case PD.library pkg_descr of
190-
Nothing ->
191-
dieWithException verbosity TestCoverageSupportLibV09
192-
Just library ->
193-
markupTest verbosity lbi distPref (prettyShow $ PD.package pkg_descr) suite library
189+
if null $ testCoverageLibsDistPref flags
190+
then dieWithException verbosity TestCoverageSupportLibV09
191+
else markupPackage verbosity flags lbi distPref pkg_descr [suite]
194192

195193
return suiteLog
196194
where

0 commit comments

Comments
 (0)