Skip to content

Commit 4755102

Browse files
Try #1548:
2 parents e0b5d9d + e01f641 commit 4755102

File tree

3 files changed

+148
-117
lines changed

3 files changed

+148
-117
lines changed

lib/cover-project.nix

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,12 @@ coverageReports:
1010
let
1111
toBashArray = arr: "(" + (lib.concatStringsSep " " arr) + ")";
1212

13-
# Create table rows for a project coverage index page that look something like:
14-
#
15-
# | Package |
16-
# |------------------|
17-
# | cardano-shell |
18-
# | cardano-launcher |
19-
coverageTableRows = coverageReport:
13+
# Create a list element for a project coverage index page.
14+
coverageListElement = coverageReport:
2015
''
21-
<tr>
22-
<td>
23-
<a href="${coverageReport.passthru.name}/hpc_index.html">${coverageReport.passthru.name}</href>
24-
</td>
25-
</tr>
16+
<li>
17+
<a href="${coverageReport.passthru.name}/hpc_index.html">${coverageReport.passthru.name}</a>
18+
</li>
2619
'';
2720

2821
projectIndexHtml = pkgs.writeText "index.html" ''
@@ -31,23 +24,32 @@ let
3124
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
3225
</head>
3326
<body>
34-
<table border="1" width="100%">
35-
<tbody>
36-
<tr>
37-
<th>Report</th>
38-
</tr>
39-
40-
${with lib; concatStringsSep "\n" (map coverageTableRows coverageReports)}
41-
42-
</tbody>
43-
</table>
27+
<div>
28+
WARNING: Modules with no coverage are not included in any of these reports, this is just how HPC works under the hood.
29+
</div>
30+
<div>
31+
<h2>Union Report</h2>
32+
<p>The following report shows how each module is covered by any test in the project:</p>
33+
<ul>
34+
<li>
35+
<a href="all/hpc_index.html">all</a>
36+
</li>
37+
</ul>
38+
</div>
39+
<div>
40+
<h2>Individual Reports</h2>
41+
<p>The following reports show how the tests of each package cover modules in the project:</p>
42+
<ul>
43+
${with lib; concatStringsSep "\n" (map coverageListElement coverageReports)}
44+
</ul>
45+
</div>
4446
</body>
4547
</html>
4648
'';
4749

4850
ghc = project.pkg-set.config.ghc.package;
4951

50-
libs = lib.remove null (map (r: r.library) coverageReports);
52+
libs = lib.unique (lib.concatMap (r: r.mixLibraries) coverageReports);
5153

5254
mixDirs =
5355
map
@@ -56,6 +58,17 @@ let
5658

5759
srcDirs = map (l: l.srcSubDirPath) libs;
5860

61+
allCoverageReport = haskellLib.coverageReport {
62+
name = "all";
63+
checks = lib.flatten (lib.concatMap
64+
(pkg: lib.optional (pkg ? checks) (lib.filter lib.isDerivation (lib.attrValues pkg.checks)))
65+
(lib.attrValues (haskellLib.selectProjectPackages project.hsPkgs)));
66+
mixLibraries = lib.concatMap
67+
(pkg: lib.optional (pkg.components ? library) pkg.components.library)
68+
(lib.attrValues (haskellLib.selectProjectPackages project.hsPkgs));
69+
ghc = project.pkg-set.config.ghc.package;
70+
};
71+
5972
in pkgs.runCommand "project-coverage-report"
6073
({ nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];
6174
LANG = "en_US.UTF-8";
@@ -124,30 +137,14 @@ in pkgs.runCommand "project-coverage-report"
124137
cp -R $report/share/hpc/vanilla/html/* $out/share/hpc/vanilla/html/
125138
'') coverageReports)}
126139
127-
if [ ''${#tixFiles[@]} -ne 0 ]; then
128-
# Create tix file with test run information for all packages
129-
tixFile="$out/share/hpc/vanilla/tix/all/all.tix"
130-
hpcSumCmd=("hpc" "sum" "--union" "--output=$tixFile")
131-
hpcSumCmd+=("''${tixFiles[@]}")
132-
echo "''${hpcSumCmd[@]}"
133-
eval "''${hpcSumCmd[@]}"
134-
135-
# Markup a HTML coverage report for the entire project
136-
cp ${projectIndexHtml} $out/share/hpc/vanilla/html/index.html
137-
echo "report coverage-per-package $out/share/hpc/vanilla/html/index.html" >> $out/nix-support/hydra-build-products
138-
139-
local markupOutDir="$out/share/hpc/vanilla/html/all"
140-
local srcDirs=${toBashArray srcDirs}
141-
local mixDirs=${toBashArray mixDirs}
142-
local allMixModules=()
143-
144-
mkdir $markupOutDir
145-
findModules allMixModules "$out/share/hpc/vanilla/mix/" "*.mix"
140+
# Copy out "all" coverage report
141+
cp -R ${allCoverageReport}/share/hpc/vanilla/tix/all $out/share/hpc/vanilla/tix
142+
cp -R ${allCoverageReport}/share/hpc/vanilla/html/all $out/share/hpc/vanilla/html
146143
147-
markup srcDirs mixDirs allMixModules "$markupOutDir" "$tixFile"
144+
# Markup a HTML coverage summary report for the entire project
145+
cp ${projectIndexHtml} $out/share/hpc/vanilla/html/index.html
148146
149-
echo "report coverage $markupOutDir/hpc_index.html" >> $out/nix-support/hydra-build-products
150-
( cd $out/share/hpc/vanilla/html ; zip -r $out/share/hpc/vanilla/html.zip . )
151-
echo "file zip $out/share/hpc/vanilla/html.zip" >> $out/nix-support/hydra-build-products
152-
fi
147+
echo "report coverage $out/share/hpc/vanilla/html/index.html" >> $out/nix-support/hydra-build-products
148+
( cd $out/share/hpc/vanilla/html ; zip -r $out/share/hpc/vanilla/html.zip . )
149+
echo "file zip $out/share/hpc/vanilla/html.zip" >> $out/nix-support/hydra-build-products
153150
''

lib/cover.nix

Lines changed: 102 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1+
# The following collects coverage information from a set of given "checks" and
2+
# provides a coverage report showing how those "checks" cover a set of given
3+
# "mixLibraries".
14
{ stdenv, lib, haskellLib, pkgs }:
25

3-
# Name of the coverage report, which should be unique
6+
# Name of the coverage report, which should be unique.
47
{ name
5-
# Library to check coverage of
6-
, library
7-
# List of check derivations that generate coverage
8-
, checks
9-
# List of other libraries to include in the coverage report. The
10-
# default value if just the derivation provided as the `library`
11-
# argument. Use a larger list of libraries if you would like the tests
12-
# of one local package to generate coverage for another.
13-
, mixLibraries ? [library]
14-
# hack for project-less projects
15-
, ghc ? library.project.pkg-set.config.ghc.package
8+
# List of check derivations that generate coverage.
9+
, checks ? []
10+
# List of libraries to include in the coverage report. If one of the above
11+
# checks generates coverage for a particular library, coverage will only
12+
# be included if that library is in this list.
13+
, mixLibraries ? []
14+
# Hack for project-less projects.
15+
, ghc ? if mixLibraries == [] then null else (lib.head mixLibraries).project.pkg-set.config.ghc.package
1616
}:
1717

1818
let
@@ -26,7 +26,7 @@ let
2626
in pkgs.runCommand (name + "-coverage-report")
2727
({nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];
2828
passthru = {
29-
inherit name library checks;
29+
inherit name checks mixLibraries;
3030
};
3131
# HPC will fail if the Haskell file contains non-ASCII characters,
3232
# unless our locale is set correctly. This has been fixed, but we
@@ -70,71 +70,105 @@ in pkgs.runCommand (name + "-coverage-report")
7070
local -n tixFs=$2
7171
local outFile="$3"
7272
73-
local hpcSumCmd=("hpc" "sum" "--union" "--output=$outFile")
73+
if (( "''${#tixFs[@]}" > 0 )); then
74+
local hpcSumCmd=("hpc" "sum" "--union" "--output=$outFile")
7475
75-
for module in "''${includedModules[@]}"; do
76-
hpcSumCmd+=("--include=$module")
77-
done
76+
for module in "''${includedModules[@]}"; do
77+
hpcSumCmd+=("--include=$module")
78+
done
7879
79-
for tixFile in "''${tixFs[@]}"; do
80-
hpcSumCmd+=("$tixFile")
81-
done
80+
for tixFile in "''${tixFs[@]}"; do
81+
hpcSumCmd+=("$tixFile")
82+
done
8283
83-
echo "''${hpcSumCmd[@]}"
84-
eval "''${hpcSumCmd[@]}"
84+
echo "''${hpcSumCmd[@]}"
85+
eval "''${hpcSumCmd[@]}"
86+
else
87+
# If there are no tix files we output an empty tix file so that we can
88+
# markup an empty HTML coverage report. This is preferable to failing to
89+
# output a HTML report.
90+
echo 'Tix []' > $outFile
91+
fi
8592
}
8693
8794
function findModules() {
88-
local searchDir=$2
95+
local -n result=$1
96+
local -n searchDirs=$2
8997
local pattern=$3
9098
91-
pushd $searchDir
92-
mapfile -d $'\0' $1 < <(find ./ -type f \
93-
-wholename "$pattern" -not -name "Paths*" \
94-
-exec basename {} \; \
95-
| sed "s/\.mix$//" \
96-
| tr "\n" "\0")
97-
popd
99+
for dir in "''${searchDirs[@]}"; do
100+
pushd $dir
101+
local temp=()
102+
mapfile -d $'\0' temp < <(find ./ -type f \
103+
-wholename "$pattern" -not -name "Paths*" \
104+
-exec basename {} \; \
105+
| sed "s/\.mix$//" \
106+
| tr "\n" "\0")
107+
result+=("''${temp[@]}")
108+
popd
109+
done
98110
}
99111
100-
local mixDirs=${toBashArray mixDirs}
101-
102112
mkdir -p $out/nix-support
103-
mkdir -p $out/share/hpc/vanilla/mix/${name}
113+
mkdir -p $out/share/hpc/vanilla/mix/
104114
mkdir -p $out/share/hpc/vanilla/tix/${name}
105115
mkdir -p $out/share/hpc/vanilla/html/${name}
106116
107-
# Copy over mix files verbatim
117+
local srcDirs=${toBashArray srcDirs}
118+
local mixDirs=${toBashArray mixDirs}
119+
120+
# Copy out mix files used for this report
108121
for dir in "''${mixDirs[@]}"; do
109122
if [ -d "$dir" ]; then
110-
cp -R "$dir"/* $out/share/hpc/vanilla/mix/${name}
123+
cp -R "$dir" $out/share/hpc/vanilla/mix/
111124
fi
112125
done
113126
114-
local srcDirs=${toBashArray srcDirs}
115-
local allMixModules=()
116-
local pkgMixModules=()
117-
118-
# The behaviour of stack coverage reports is to provide tix files
119-
# that include coverage information for every local package, but
120-
# to provide HTML reports that only include coverage info for the
121-
# current package. We emulate the same behaviour here. If the user
122-
# includes all local packages in the mix libraries argument, they
123-
# will get a coverage report very similar to stack.
124-
125-
# All mix modules
126-
findModules allMixModules "$out/share/hpc/vanilla/mix/${name}" "*.mix"
127-
# Only mix modules corresponding to this package
128-
findModules pkgMixModules "$out/share/hpc/vanilla/mix/${name}" "*${name}*/*.mix"
129-
130-
# For each test
127+
local mixModules=()
128+
# Mix modules for all packages in "mixLibraries"
129+
findModules mixModules mixDirs "*.mix"
130+
131+
# We need to make a distinction between library "exposed-modules" and
132+
# "other-modules" used in test suites:
133+
# - "exposed-modules" are addressed as "$library-$version-$hash/module"
134+
# - "other-modules" are addressed as "module"
135+
#
136+
# This complicates the code required to find the mix modules. For a given mix directory:
137+
#
138+
# mix
139+
# └── ntp-client-0.0.1
140+
# └── ntp-client-0.0.1-gYjRsBHUCaHX7ENcjHnw5
141+
# ├── Network.NTP.Client.mix
142+
# ├── Network.NTP.Client.Packet.mix
143+
# └── Network.NTP.Client.Query.mix
144+
#
145+
# Iff ntp-client uses "other-modules" in a test suite, both:
146+
# - "mix/ntp-client-0.0.1", and
147+
# - "mix/ntp-client-0.0.1/ntp-client-0.0.1-gYjRsBHUCaHX7ENcjHnw5"
148+
# need to be provided to hpc as search directories.
149+
#
150+
# I'd prefer to just exclude "other-modules", but I can't think of an easy
151+
# way to do that in bash.
152+
#
153+
# Here we expand the search dirs and modify the mix dirs accordingly:
154+
for dir in "''${mixDirs[@]}"; do
155+
local otherModulesSearchDirs=()
156+
# Simply consider any directory with a mix file as a search directory.
157+
mapfile -d $'\0' otherModulesSearchDirs < <(find $dir -type f \
158+
-wholename "*.mix" \
159+
-exec dirname {} \; \
160+
| uniq \
161+
| tr "\n" "\0")
162+
mixDirs+=("''${otherModulesSearchDirs[@]}")
163+
done
164+
131165
local tixFiles=()
132166
${lib.concatStringsSep "\n" (builtins.map (check: ''
133167
if [ -d "${check}/share/hpc/vanilla/tix" ]; then
134168
pushd ${check}/share/hpc/vanilla/tix
135169
136170
tixFile="$(find . -iwholename "*.tix" -type f -print -quit)"
137-
local newTixFile=$out/share/hpc/vanilla/tix/${name}/"$tixFile"
171+
local newTixFile=$out/share/hpc/vanilla/tix/${check.name}/"$(basename $tixFile)"
138172
139173
mkdir -p "$(dirname $newTixFile)"
140174
# Copy over the tix file verbatim
@@ -143,29 +177,28 @@ in pkgs.runCommand (name + "-coverage-report")
143177
# Add the tix file to our list
144178
tixFiles+=("$newTixFile")
145179
146-
# Create a coverage report for *just that test*
147-
markup srcDirs mixDirs pkgMixModules "$out/share/hpc/vanilla/html/${name}/${check.exeName}/" "$newTixFile"
180+
# Create a coverage report for *just that check* affecting any of the
181+
# "mixLibraries"
182+
markup srcDirs mixDirs mixModules "$out/share/hpc/vanilla/html/${check.name}/" "$newTixFile"
148183
149184
popd
150185
fi
151186
'') checks)
152187
}
153188
154-
# Sum tix files to create a tix file with all relevant tix
155-
# information and markup a HTML report from this info.
156-
if (( "''${#tixFiles[@]}" > 0 )); then
157-
local sumTixFile="$out/share/hpc/vanilla/tix/${name}/${name}.tix"
158-
local markupOutDir="$out/share/hpc/vanilla/html/${name}"
189+
# Sum tix files to create a tix file with tix information from all tests in
190+
# the package and markup a HTML report from this info.
191+
local sumTixFile="$out/share/hpc/vanilla/tix/${name}/${name}.tix"
192+
local markupOutDir="$out/share/hpc/vanilla/html/${name}"
159193
160-
# Sum all of our tix file, including modules from any local package
161-
sumTix allMixModules tixFiles "$sumTixFile"
194+
# Sum all of our tix files
195+
sumTix mixModules tixFiles "$sumTixFile"
162196
163-
# Markup a HTML report, included modules from only this package
164-
markup srcDirs mixDirs pkgMixModules "$markupOutDir" "$sumTixFile"
197+
# Markup a HTML report
198+
markup srcDirs mixDirs mixModules "$markupOutDir" "$sumTixFile"
165199
166-
# Provide a HTML zipfile and Hydra links
167-
( cd "$markupOutDir" ; zip -r $out/share/hpc/vanilla/${name}-html.zip . )
168-
echo "report coverage $markupOutDir/hpc_index.html" >> $out/nix-support/hydra-build-products
169-
echo "file zip $out/share/hpc/vanilla/${name}-html.zip" >> $out/nix-support/hydra-build-products
170-
fi
200+
# Provide a HTML zipfile and Hydra links
201+
( cd "$markupOutDir" ; zip -r $out/share/hpc/vanilla/${name}-html.zip . )
202+
echo "report coverage $markupOutDir/hpc_index.html" >> $out/nix-support/hydra-build-products
203+
echo "file zip $out/share/hpc/vanilla/${name}-html.zip" >> $out/nix-support/hydra-build-products
171204
''

overlays/haskell.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,8 +591,9 @@ final: prev: {
591591

592592
coverageReport = haskellLib.coverageReport (rec {
593593
name = package.identifier.name + "-" + package.identifier.version;
594-
library = if components ? library then components.library else null;
594+
# Include the checks for a single package.
595595
checks = final.lib.filter (final.lib.isDerivation) (final.lib.attrValues package'.checks);
596+
# Checks from that package may provide coverage information for any library in the project.
596597
mixLibraries = final.lib.concatMap
597598
(pkg: final.lib.optional (pkg.components ? library) pkg.components.library)
598599
(final.lib.attrValues (haskellLib.selectProjectPackages project.hsPkgs));

0 commit comments

Comments
 (0)