@@ -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
1{ stdenv, lib, haskellLib, pkgs }:
4{ stdenv, lib, haskellLib, pkgs }:
3−# Name of the coverage report, which should be unique
6+# Name of the coverage report, which should be unique.
5−# Library to check coverage of
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
@@ -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;
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"
73+ if (( "''${#tixFs[@]}" > 0 )); then
73− local hpcSumCmd=("hpc" "sum" "--union" "--output=$outFile")
74+ local hpcSumCmd=("hpc" "sum" "--union" "--output=$outFile")
75− for module in "''${includedModules[@]}"; do
76+ for module in "''${includedModules[@]}"; do
76− hpcSumCmd+=("--include=$module")
77+ hpcSumCmd+=("--include=$module")
79− for tixFile in "''${tixFs[@]}"; do
80+ for tixFile in "''${tixFs[@]}"; do
80− hpcSumCmd+=("$tixFile")
81+ hpcSumCmd+=("$tixFile")
83− echo "''${hpcSumCmd[@]}"
84+ echo "''${hpcSumCmd[@]}"
84− eval "''${hpcSumCmd[@]}"
85+ eval "''${hpcSumCmd[@]}"
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
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
99+ for dir in "''${searchDirs[@]}"; do
91− pushd $searchDir
100+ pushd $dir
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[@]}")
100− local mixDirs=${toBashArray mixDirs}
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}
117+ local srcDirs=${toBashArray srcDirs}
118+ local mixDirs=${toBashArray mixDirs}
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/
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"
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:
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
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.
150+ # I'd prefer to just exclude "other-modules", but I can't think of an easy
151+ # way to do that in bash.
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 {} \; \
162+ mixDirs+=("''${otherModulesSearchDirs[@]}")
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
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)"
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")
146− # Create a coverage report for *just that test*
180+ # Create a coverage report for *just that check* affecting any of the
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"
151 '') checks)
186 '') checks)
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}"
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"
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"
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