on iko/ghcjs-patch-master help sign in
Improve coverage reports (#1548)
0dca71eSamuel Evans-Powell
lib/cover-project.nix+44−47
@@ -10,19 +10,12 @@ coverageReports:
10let
10let
11 toBashArray = arr: "(" + (lib.concatStringsSep " " arr) + ")";
11 toBashArray = arr: "(" + (lib.concatStringsSep " " arr) + ")";
12
12
13− # Create table rows for a project coverage index page that look something like:
13+ # Create a list element for a project coverage index page.
14− #
15− # | Package |
16− # |------------------|
17− # | cardano-shell |
18− # | cardano-launcher |
19− coverageTableRows = coverageReport:
14+ coverageListElement = coverageReport:
20 ''
15 ''
21− <tr>
16+ <li>
22− <td>
23− <a href="${coverageReport.passthru.name}/hpc_index.html">${coverageReport.passthru.name}</href>
17+ <a href="${coverageReport.passthru.name}/hpc_index.html">${coverageReport.passthru.name}</a>
24− </td>
18+ </li>
25− </tr>
26 '';
19 '';
27
20
28 projectIndexHtml = pkgs.writeText "index.html" ''
21 projectIndexHtml = pkgs.writeText "index.html" ''
@@ -31,23 +24,32 @@ let
31 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
24 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
32 </head>
25 </head>
33 <body>
26 <body>
34− <table border="1" width="100%">
27+ <div>
35− <tbody>
28+ WARNING: Modules with no coverage are not included in any of these reports, this is just how HPC works under the hood.
36− <tr>
29+ </div>
30+ <div>
37− <th>Report</th>
31+ <h2>Union Report</h2>
38− </tr>
32+ <p>The following report shows how each module is covered by any test in the project:</p>
39−
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>
40− ${with lib; concatStringsSep "\n" (map coverageTableRows coverageReports)}
43+ ${with lib; concatStringsSep "\n" (map coverageListElement coverageReports)}
41−
44+ </ul>
42− </tbody>
45+ </div>
43− </table>
44 </body>
46 </body>
45 </html>
47 </html>
46 '';
48 '';
47
49
48 ghc = project.pkg-set.config.ghc.package;
50 ghc = project.pkg-set.config.ghc.package;
49
51
50− libs = lib.remove null (map (r: r.library) coverageReports);
52+ libs = lib.unique (lib.concatMap (r: r.mixLibraries) coverageReports);
51
53
52 mixDirs =
54 mixDirs =
53 map
55 map
@@ -56,6 +58,17 @@ let
56
58
57 srcDirs = map (l: l.srcSubDirPath) libs;
59 srcDirs = map (l: l.srcSubDirPath) libs;
58
60
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+
59in pkgs.runCommand "project-coverage-report"
72in pkgs.runCommand "project-coverage-report"
60 ({ nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];
73 ({ nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];
61 LANG = "en_US.UTF-8";
74 LANG = "en_US.UTF-8";
@@ -124,30 +137,14 @@ in pkgs.runCommand "project-coverage-report"
124 cp -R $report/share/hpc/vanilla/html/* $out/share/hpc/vanilla/html/
137 cp -R $report/share/hpc/vanilla/html/* $out/share/hpc/vanilla/html/
125 '') coverageReports)}
138 '') coverageReports)}
126
139
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
140+ # Copy out "all" coverage report
136− cp ${projectIndexHtml} $out/share/hpc/vanilla/html/index.html
141+ cp -R ${allCoverageReport}/share/hpc/vanilla/tix/all $out/share/hpc/vanilla/tix
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"
142+ cp -R ${allCoverageReport}/share/hpc/vanilla/html/all $out/share/hpc/vanilla/html
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"
146
143
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
148
146
149− echo "report coverage $markupOutDir/hpc_index.html" >> $out/nix-support/hydra-build-products
147+ echo "report coverage $out/share/hpc/vanilla/html/index.html" >> $out/nix-support/hydra-build-products
150− ( cd $out/share/hpc/vanilla/html ; zip -r $out/share/hpc/vanilla/html.zip . )
148+ ( 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
149+ echo "file zip $out/share/hpc/vanilla/html.zip" >> $out/nix-support/hydra-build-products
152− fi
153 ''
150 ''
lib/cover.nix+102−69
@@ -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".
1{ stdenv, lib, haskellLib, pkgs }:
4{ stdenv, lib, haskellLib, pkgs }:
2
5
3−# Name of the coverage report, which should be unique
6+# Name of the coverage report, which should be unique.
4{ name
7{ name
5−# Library to check coverage of
6−, library
7−# List of check derivations that generate coverage
8+# List of check derivations that generate coverage.
8−, checks
9+, checks ? []
9−# List of other libraries to include in the coverage report. The
10+# List of libraries to include in the coverage report. If one of the above
10−# default value if just the derivation provided as the `library`
11+# checks generates coverage for a particular library, coverage will only
11−# argument. Use a larger list of libraries if you would like the tests
12+# be included if that library is in this list.
12−# of one local package to generate coverage for another.
13−, mixLibraries ? [library]
13+, mixLibraries ? []
14−# hack for project-less projects
14+# Hack for project-less projects.
15−, ghc ? library.project.pkg-set.config.ghc.package
15+, ghc ? if mixLibraries == [] then null else (lib.head mixLibraries).project.pkg-set.config.ghc.package
16}:
16}:
17
17
18let
18let
@@ -26,7 +26,7 @@ let
26in pkgs.runCommand (name + "-coverage-report")
26in pkgs.runCommand (name + "-coverage-report")
27 ({nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];
27 ({nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];
28 passthru = {
28 passthru = {
29− inherit name library checks;
29+ inherit name checks mixLibraries;
30 };
30 };
31 # HPC will fail if the Haskell file contains non-ASCII characters,
31 # HPC will fail if the Haskell file contains non-ASCII characters,
32 # unless our locale is set correctly. This has been fixed, but we
32 # unless our locale is set correctly. This has been fixed, but we
@@ -70,71 +70,105 @@ in pkgs.runCommand (name + "-coverage-report")
70 local -n tixFs=$2
70 local -n tixFs=$2
71 local outFile="$3"
71 local outFile="$3"
72
72
73+ if (( "''${#tixFs[@]}" > 0 )); then
73− local hpcSumCmd=("hpc" "sum" "--union" "--output=$outFile")
74+ local hpcSumCmd=("hpc" "sum" "--union" "--output=$outFile")
74
75
75− for module in "''${includedModules[@]}"; do
76+ for module in "''${includedModules[@]}"; do
76− hpcSumCmd+=("--include=$module")
77+ hpcSumCmd+=("--include=$module")
77− done
78+ done
78
79
79− for tixFile in "''${tixFs[@]}"; do
80+ for tixFile in "''${tixFs[@]}"; do
80− hpcSumCmd+=("$tixFile")
81+ hpcSumCmd+=("$tixFile")
81− done
82+ done
82
83
83− echo "''${hpcSumCmd[@]}"
84+ echo "''${hpcSumCmd[@]}"
84− eval "''${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
85 }
92 }
86
93
87 function findModules() {
94 function findModules() {
88− local searchDir=$2
95+ local -n result=$1
96+ local -n searchDirs=$2
89 local pattern=$3
97 local pattern=$3
90
98
99+ for dir in "''${searchDirs[@]}"; do
91− pushd $searchDir
100+ pushd $dir
101+ local temp=()
92− mapfile -d $'\0' $1 < <(find ./ -type f \
102+ mapfile -d $'\0' temp < <(find ./ -type f \
93− -wholename "$pattern" -not -name "Paths*" \
103+ -wholename "$pattern" -not -name "Paths*" \
94− -exec basename {} \; \
104+ -exec basename {} \; \
95− | sed "s/\.mix$//" \
105+ | sed "s/\.mix$//" \
96− | tr "\n" "\0")
106+ | tr "\n" "\0")
107+ result+=("''${temp[@]}")
97− popd
108+ popd
109+ done
98 }
110 }
99
111
100− local mixDirs=${toBashArray mixDirs}
101−
102 mkdir -p $out/nix-support
112 mkdir -p $out/nix-support
103− mkdir -p $out/share/hpc/vanilla/mix/${name}
113+ mkdir -p $out/share/hpc/vanilla/mix/
104 mkdir -p $out/share/hpc/vanilla/tix/${name}
114 mkdir -p $out/share/hpc/vanilla/tix/${name}
105 mkdir -p $out/share/hpc/vanilla/html/${name}
115 mkdir -p $out/share/hpc/vanilla/html/${name}
106
116
117+ local srcDirs=${toBashArray srcDirs}
118+ local mixDirs=${toBashArray mixDirs}
119+
107− # Copy over mix files verbatim
120+ # Copy out mix files used for this report
108 for dir in "''${mixDirs[@]}"; do
121 for dir in "''${mixDirs[@]}"; do
109 if [ -d "$dir" ]; then
122 if [ -d "$dir" ]; then
110− cp -R "$dir"/* $out/share/hpc/vanilla/mix/${name}
123+ cp -R "$dir" $out/share/hpc/vanilla/mix/
111 fi
124 fi
112 done
125 done
113
126
114− local srcDirs=${toBashArray srcDirs}
127+ local mixModules=()
115− local allMixModules=()
128+ # Mix modules for all packages in "mixLibraries"
116− local pkgMixModules=()
129+ findModules mixModules mixDirs "*.mix"
117−
130+
118− # The behaviour of stack coverage reports is to provide tix files
131+ # We need to make a distinction between library "exposed-modules" and
119− # that include coverage information for every local package, but
132+ # "other-modules" used in test suites:
120− # to provide HTML reports that only include coverage info for the
133+ # - "exposed-modules" are addressed as "$library-$version-$hash/module"
121− # current package. We emulate the same behaviour here. If the user
134+ # - "other-modules" are addressed as "module"
122− # includes all local packages in the mix libraries argument, they
135+ #
123− # will get a coverage report very similar to stack.
136+ # This complicates the code required to find the mix modules. For a given mix directory:
124−
137+ #
125− # All mix modules
138+ # mix
126− findModules allMixModules "$out/share/hpc/vanilla/mix/${name}" "*.mix"
139+ # └── ntp-client-0.0.1
127− # Only mix modules corresponding to this package
140+ # └── ntp-client-0.0.1-gYjRsBHUCaHX7ENcjHnw5
128− findModules pkgMixModules "$out/share/hpc/vanilla/mix/${name}" "*${name}*/*.mix"
141+ # ├── Network.NTP.Client.mix
129−
142+ # ├── Network.NTP.Client.Packet.mix
130− # For each test
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+
131 local tixFiles=()
165 local tixFiles=()
132 ${lib.concatStringsSep "\n" (builtins.map (check: ''
166 ${lib.concatStringsSep "\n" (builtins.map (check: ''
133 if [ -d "${check}/share/hpc/vanilla/tix" ]; then
167 if [ -d "${check}/share/hpc/vanilla/tix" ]; then
134 pushd ${check}/share/hpc/vanilla/tix
168 pushd ${check}/share/hpc/vanilla/tix
135
169
136 tixFile="$(find . -iwholename "*.tix" -type f -print -quit)"
170 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)"
138
172
139 mkdir -p "$(dirname $newTixFile)"
173 mkdir -p "$(dirname $newTixFile)"
140 # Copy over the tix file verbatim
174 # Copy over the tix file verbatim
@@ -143,29 +177,28 @@ in pkgs.runCommand (name + "-coverage-report")
143 # Add the tix file to our list
177 # Add the tix file to our list
144 tixFiles+=("$newTixFile")
178 tixFiles+=("$newTixFile")
145
179
146− # Create a coverage report for *just that test*
180+ # Create a coverage report for *just that check* affecting any of the
181+ # "mixLibraries"
147− markup srcDirs mixDirs pkgMixModules "$out/share/hpc/vanilla/html/${name}/${check.exeName}/" "$newTixFile"
182+ markup srcDirs mixDirs mixModules "$out/share/hpc/vanilla/html/${check.name}/" "$newTixFile"
148
183
149 popd
184 popd
150 fi
185 fi
151 '') checks)
186 '') checks)
152 }
187 }
153
188
154− # Sum tix files to create a tix file with all relevant tix
189+ # Sum tix files to create a tix file with tix information from all tests in
155− # information and markup a HTML report from this info.
190+ # the package and markup a HTML report from this info.
156− if (( "''${#tixFiles[@]}" > 0 )); then
157− local sumTixFile="$out/share/hpc/vanilla/tix/${name}/${name}.tix"
191+ local sumTixFile="$out/share/hpc/vanilla/tix/${name}/${name}.tix"
158− local markupOutDir="$out/share/hpc/vanilla/html/${name}"
192+ local markupOutDir="$out/share/hpc/vanilla/html/${name}"
159
193
160− # Sum all of our tix file, including modules from any local package
194+ # Sum all of our tix files
161− sumTix allMixModules tixFiles "$sumTixFile"
195+ sumTix mixModules tixFiles "$sumTixFile"
162
196
163− # Markup a HTML report, included modules from only this package
197+ # Markup a HTML report
164− markup srcDirs mixDirs pkgMixModules "$markupOutDir" "$sumTixFile"
198+ markup srcDirs mixDirs mixModules "$markupOutDir" "$sumTixFile"
165
199
166− # Provide a HTML zipfile and Hydra links
200+ # Provide a HTML zipfile and Hydra links
167− ( cd "$markupOutDir" ; zip -r $out/share/hpc/vanilla/${name}-html.zip . )
201+ ( 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
202+ 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
203+ echo "file zip $out/share/hpc/vanilla/${name}-html.zip" >> $out/nix-support/hydra-build-products
170− fi
171 ''
204 ''
overlays/haskell.nix+2−1
@@ -591,8 +591,9 @@ final: prev: {
591
591
592 coverageReport = haskellLib.coverageReport (rec {
592 coverageReport = haskellLib.coverageReport (rec {
593 name = package.identifier.name + "-" + package.identifier.version;
593 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.
595 checks = final.lib.filter (final.lib.isDerivation) (final.lib.attrValues package'.checks);
595 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.
596 mixLibraries = final.lib.concatMap
597 mixLibraries = final.lib.concatMap
597 (pkg: final.lib.optional (pkg.components ? library) pkg.components.library)
598 (pkg: final.lib.optional (pkg.components ? library) pkg.components.library)
598 (final.lib.attrValues (haskellLib.selectProjectPackages project.hsPkgs));
599 (final.lib.attrValues (haskellLib.selectProjectPackages project.hsPkgs));