on iko/ghcjs-patch-master help sign in
Add support for coverage (#762)
48b8674Samuel Evans-Powell
builder/comp-builder.nix+9
@@ -53,6 +53,9 @@ let self =
53, enableExecutableProfiling ? component.enableExecutableProfiling
53, enableExecutableProfiling ? component.enableExecutableProfiling
54, profilingDetail ? component.profilingDetail
54, profilingDetail ? component.profilingDetail
55
55
56+# Coverage
57+, doCoverage ? component.doCoverage
58+
56# Data
59# Data
57, enableSeparateDataOutput ? component.enableSeparateDataOutput
60, enableSeparateDataOutput ? component.enableSeparateDataOutput
58
61
@@ -118,6 +121,7 @@ let
118 (enableFeature enableExecutableProfiling "executable-profiling")
121 (enableFeature enableExecutableProfiling "executable-profiling")
119 (enableFeature enableStatic "static")
122 (enableFeature enableStatic "static")
120 (enableFeature enableShared "shared")
123 (enableFeature enableShared "shared")
124+ (enableFeature doCoverage "coverage")
121 ] ++ lib.optionals (stdenv.hostPlatform.isMusl && (haskellLib.isExecutableType componentId)) [
125 ] ++ lib.optionals (stdenv.hostPlatform.isMusl && (haskellLib.isExecutableType componentId)) [
122 # These flags will make sure the resulting executable is statically linked.
126 # These flags will make sure the resulting executable is statically linked.
123 # If it uses other libraries it may be necessary for to add more
127 # If it uses other libraries it may be necessary for to add more
@@ -358,6 +362,11 @@ let
358 fi
362 fi
359 done
363 done
360 '')
364 '')
365+ + (lib.optionalString doCoverage ''
366+ mkdir -p $out/share
367+ cp -r dist/hpc $out/share
368+ cp dist/setup-config $out/
369+ '')
361 }
370 }
362 runHook postInstall
371 runHook postInstall
363 '' + (lib.optionalString keepSource ''
372 '' + (lib.optionalString keepSource ''
changelog.md+9
@@ -1,6 +1,15 @@
1This file contains a summary of changes to Haskell.nix and `nix-tools`
1This file contains a summary of changes to Haskell.nix and `nix-tools`
2that will impact users.
2that will impact users.
3
3
4+## Sep 8, 2020
5+* Added the ability to generate coverage reports for packages and
6+ projects.
7+* Added the `doCoverage` module option that allows users to choose
8+ packages to enable coverage for.
9+* Added a `doCoverage` flag to the component builder that outputs HPC
10+ information when coverage is enabled.
11+* Added test for coverage.
12+
4## July 21, 2020
13## July 21, 2020
5* Removed `components.all`, use `symlinkJoin` on components.exes or
14* Removed `components.all`, use `symlinkJoin` on components.exes or
6 `shellFor` if you need a shell.
15 `shellFor` if you need a shell.
docs/dev/coverage.mdnew file+180
@@ -0,0 +1,180 @@
1+# Developer Coverage Overview
2+
3+## Building
4+
5+The implementation of coverage starts with the "doCoverage" flag on
6+the builder in `comp-builder.nix`. The doCoverage flag enables and
7+disables the Cabal coverage flag and copies any generated coverage
8+data to "$out/share/hpc".
9+
10+### Mix and tix files
11+
12+The coverage information for any derivation consists of "mix" and
13+"tix" files.
14+
15+Mix files record static information about a source file and are
16+generated at build time. They primarily contain a path to the source
17+file and information about expressions and regions of the source file,
18+which are later referenced by tix files.
19+
20+Tix files contain dynamic information about a test run, recording when
21+a portion of a source file is touched by a test. These are generated
22+when the test is run.
23+
24+### Multiple local packages
25+
26+In the context of multiple local packages, there are a few types of
27+coverage we might be interested in:
28+ - How well does the tests for this package cover the package library?
29+ - How well does the tests for this package cover the libraries of
30+ other packages in this project?
31+ - Both of the above.
32+
33+To facilitate expressing any of these classifications of coverage, the
34+`lib/cover.nix` function provides the `mixLibraries` argument. If
35+you're just interested in how the tests cover the package library, you
36+provide that library as an argument to `mixLibraries`. If you're
37+interested in how the tests also cover other local packages in the
38+project, you can also provide those libraries as arguments to
39+mixLibraries.
40+
41+The `projectCoverageReport` and `coverageReport` attributes that are
42+provided by default on projects and packages respectively provide
43+coverage information for *all* local packages in the project. This is
44+to mimic the behaviour of Stack, which seems to be the expectation of
45+most people. Of course, you can use the `projectCoverageReport` and
46+`coverageReport` functions to construct your own custom coverage
47+reports (as detailed in the [coverage tutorial](../tutorials/coverage.md#custom)).
48+
49+## Coverage reports
50+
51+### Package reports
52+
53+The coverage information generated will look something like this:
54+
55+```bash
56+/nix/store/...-my-project-0.1.0.0-coverage-report/
57+└── share
58+ └── hpc
59+ └── vanilla
60+ ├── html
61+ │   └── my-library-0.1.0.0
62+ │   ├── my-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
63+ │ │ ├── My.Lib.Config.hs.html
64+ │ │ ├── My.Lib.Types.hs.html
65+ │ │ └── My.Lib.Util.hs.html
66+ │   ├── hpc_index_alt.html
67+ │   ├── hpc_index_exp.html
68+ │   ├── hpc_index_fun.html
69+ │   └── hpc_index.html
70+ ├── mix
71+ │   └── my-library-0.1.0.0
72+ │   └── my-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
73+ │      ├── My.Lib.Config.mix
74+ │      ├── My.Lib.Types.mix
75+ │      └── My.Lib.Util.mix
76+ └── tix
77+ └── my-library-0.1.0.0
78+ ├── my-library-0.1.0.0.tix
79+ ├── my-test-1
80+ │   └── my-test-1.tix
81+ └── unit-test
82+ └── unit-test.tix
83+```
84+
85+- The mix files are copied verbatim from the library built with
86+ coverage.
87+- The tix files for each test are copied from the check run verbatim
88+ and are output to ".../tix/<libraryname>/<testname>/<testname>.tix".
89+- The tix files for each library are generated by summing the tix
90+ files for each test, but excluding any test modules. This tix file
91+ is output to ".../tix/<libraryname>/<libraryname>.tix".
92+ - Test modules are determined by inspecting the plan for the project
93+ (i.e. for the project "my-project" and test-suite "my-test-1", the
94+ test modules are read from:
95+ `my-project.checks.my-test-1.config.modules`)
96+- The hpc HTML reports for each library are generated from their
97+ respective tix files (i.e. the
98+ `share/hpc/vanilla/html/my-library-0.1.0.0` report is generated from
99+ the
100+ `share/hpc/vanilla/tix/my-library-0.1.0.0/my-library-0.1.0.0.tix`
101+ file)
102+
103+### Project-wide reports
104+
105+The coverage information for an entire project will look something
106+like this:
107+
108+```bash
109+/nix/store/...-coverage-report
110+└── share
111+ └── hpc
112+ └── vanilla
113+ ├── html
114+ │   ├── index.html
115+ │   ├── all
116+ │   │   ├── my-library-0.1.0.0-ERSaOroBZhe9awsoBkhmcV
117+ │ │   │ ├── My.Lib.Config.hs.html
118+ │ │   │ ├── My.Lib.Types.hs.html
119+ │ │   │ └── My.Lib.Util.hs.html
120+ │ │ ├── other-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
121+ │ │ │   ├── Other.Lib.A.hs.html
122+ │ │ │   └── Other.Lib.B.hs.html
123+ │   │   ├── hpc_index_alt.html
124+ │   │   ├── hpc_index_exp.html
125+ │   │   ├── hpc_index_fun.html
126+ │   │   └── hpc_index.html
127+ │   ├── my-library-0.1.0.0
128+ │   │   ├── my-library-0.1.0.0-ERSaOroBZhe9awsoBkhmcV
129+ │ │   │ ├── My.Lib.Config.hs.html
130+ │ │   │ ├── My.Lib.Types.hs.html
131+ │ │   │ └── My.Lib.Util.hs.html
132+ │   │   ├── hpc_index_alt.html
133+ │   │   ├── hpc_index_exp.html
134+ │   │   ├── hpc_index_fun.html
135+ │   │   └── hpc_index.html
136+ │ └── other-libray-0.1.0.0
137+ │ ├── other-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
138+ │ │   ├── Other.Lib.A.hs.html
139+ │ │   └── Other.Lib.B.hs.html
140+ │ ├── hpc_index_alt.html
141+ │ ├── hpc_index_exp.html
142+ │ ├── hpc_index_fun.html
143+ │ └── hpc_index.html
144+ ├── mix
145+ │   ├── my-library-0.1.0.0-ERSaOroBZhe9awsoBkhmcV
146+ │   │   ├── My.Lib.Config.mix
147+ │   │   ├── My.Lib.Types.mix
148+ │   │   └── My.Lib.Util.mix
149+ │   └── other-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
150+ │   ├── Other.Lib.A.mix
151+ │   └── Other.Lib.B.mix
152+ └── tix
153+ ├── all
154+ │   └── all.tix
155+ ├── my-library-0.1.0.0
156+ │ ├── my-library-0.1.0.0.tix
157+ │ ├── my-test-1
158+ │ │   └── my-test-1.tix
159+ │ └── unit-test
160+ │ └── unit-test.tix
161+ └── another-library-0.1.0.0
162+ ├── another-library-0.1.0.0.tix
163+ ├── my-test-2
164+ │   └── my-test-2.tix
165+ └── unit-test
166+ └── unit-test.tix
167+```
168+
169+All of the coverage information is copied verbatim from the coverage
170+reports for each of the constituent packages. A few additions are
171+made:
172+ - `tix/all/all.tix` is generated from the union of all the library
173+ tix files.
174+ - We use this file when generating coverage reports for
175+ "coveralls.io".
176+ - An index page (`html/index.html`) is generated which links to the
177+ HTML coverage reports of the constituent packages.
178+ - A synthetic HTML report is generated from the `tix/all/all.tix`
179+ file. This shows the union of all the coverage information
180+ generated by each constituent coverage report.
docs/tutorials/coverage.mdnew file+123
@@ -0,0 +1,123 @@
1+# Coverage
2+
3+haskell.nix can generate coverage information for your package or
4+project using Cabal's inbuilt hpc support.
5+
6+## Prerequisites
7+
8+To get a sensible coverage report, you need to enable coverage on each
9+of the packages of your project:
10+
11+```nix
12+pkgs.haskell-nix.project {
13+ src = pkgs.haskell-nix.haskellLib.cleanGit {
14+ name = "haskell-nix-project";
15+ src = ./.;
16+ };
17+ compiler-nix-name = "ghc884";
18+
19+ modules = [{
20+ packages.$pkg.components.library.doCoverage = true;
21+ }];
22+}
23+```
24+
25+If you would like to make coverage optional, add an argument to your nix expression:
26+
27+```nix
28+{ withCoverage ? false }:
29+
30+pkgs.haskell-nix.project {
31+ src = pkgs.haskell-nix.haskellLib.cleanGit {
32+ name = "haskell-nix-project";
33+ src = ./.;
34+ };
35+ compiler-nix-name = "ghc884";
36+
37+ modules = pkgs.lib.optional withCoverage [{
38+ packages.$pkg.components.library.doCoverage = true;
39+ }];
40+}
41+```
42+
43+## Per-package
44+
45+```bash
46+nix-build default.nix -A "projectWithCoverage.$pkg.coverageReport"
47+```
48+
49+This will generate a coverage report for the package you requested.
50+All tests that are enabled (configured with `doCheck == true`) are
51+included in the coverage report.
52+
53+See the [developer coverage docs](../dev/coverage.md#package-reports) for more information.
54+
55+## Project-wide
56+
57+```bash
58+nix-build default.nix -A "projectWithCoverage.projectCoverageReport"
59+```
60+
61+This will generate a coverage report for all the local packages in
62+your project.
63+
64+See the [developer coverage docs](../dev/coverage.md#project-wide-reports) for more information.
65+
66+## Custom
67+
68+By default, the behaviour of the `coverageReport` attribute is to
69+generate a coverage report that describes how that package affects the
70+coverage of all local packages (including itself) in the project.
71+
72+The default behaviour of `projectCoverageReport` is to sum the
73+default coverage reports (produced by the above process) of all local
74+packages in the project.
75+
76+You can modify this behaviour by using the `coverageReport` and
77+`projectCoverageReport` functions found in the haskell.nix library:
78+
79+```nix
80+let
81+ inherit (pkgs.haskell-nix) haskellLib;
82+
83+ project = haskellLib.project {
84+ src = pkgs.haskell-nix.haskellLib.cleanGit {
85+ name = "haskell-nix-project";
86+ src = ./.;
87+ };
88+ compiler-nix-name = "ghc884";
89+
90+ modules = [{
91+ packages.$pkgA.components.library.doCoverage = true;
92+ packages.$pkgB.components.library.doCoverage = true;
93+ }];
94+ };
95+
96+ # Generate a coverage report for $pkgA that only includes the
97+ # unit-test check and only shows coverage information for $pkgA, not
98+ # $pkgB.
99+ custom$pkgACoverageReport = haskellLib.coverageReport rec {
100+ name = "$pkgA-unit-tests-only"
101+ inherit (project.$pkgA.components) library;
102+ checks = [project.$pkgA.components.checks.unit-test];
103+ # Note that this is the default value of the "mixLibraries"
104+ # argument and so this line isn't really necessary.
105+ mixLibraries = [project.$pkgA.components.library];
106+ };
107+
108+ custom$pkgBCoverageReport = haskellLib.coverageReport rec {
109+ name = "$pkgB-unit-tests-only"
110+ inherit (project.$pkgB.components) library;
111+ checks = [project.$pkgB.components.checks.unit-test];
112+ mixLibraries = [project.$pkgB.components.library];
113+ };
114+
115+ # Generate a project coverage report that only includes the unit
116+ # tests of the project, and only shows how each unit test effects
117+ # the coverage of it's package, and not other packages in the
118+ # project.
119+ allUnitTestsProjectReport = haskellLib.projectCoverageReport [custom$pkgACoverageReport custom$pkgBCoverageReport];
120+in {
121+ inherit project custom$pkgACoverageReport custom$pkgBCoverageReport allUnitTestsProjectCoverageReport;
122+}
123+```
lib/check.nix+6−3
@@ -15,7 +15,7 @@ in stdenv.mkDerivation ({
15 src = drv.source or (srcOnly drv);
15 src = drv.source or (srcOnly drv);
16
16
17 passthru = {
17 passthru = {
18− inherit (drv) identifier config configFiles executableToolDepends cleanSrc env;
18+ inherit (drv) identifier config configFiles executableToolDepends cleanSrc env exeName;
19 };
19 };
20
20
21 inherit (drv) meta LANG LC_ALL buildInputs nativeBuildInputs;
21 inherit (drv) meta LANG LC_ALL buildInputs nativeBuildInputs;
@@ -27,11 +27,14 @@ in stdenv.mkDerivation ({
27 # If doCheck or doCrossCheck are false we may still build this
27 # If doCheck or doCrossCheck are false we may still build this
28 # component and we want it to quietly succeed.
28 # component and we want it to quietly succeed.
29 buildPhase = ''
29 buildPhase = ''
30− touch $out
30+ mkdir $out
31
31
32 runHook preCheck
32 runHook preCheck
33
33
34− ${toString component.testWrapper} ${drv}/bin/${drv.exeName} ${lib.concatStringsSep " " component.testFlags} | tee $out
34+ ${toString component.testWrapper} ${drv}/bin/${drv.exeName} ${lib.concatStringsSep " " component.testFlags} | tee $out/test-stdout
35+
36+ # Copy over tix files, if they exist
37+ find . -iname '${drv.exeName}.tix' -exec mkdir -p $out/share/hpc/vanilla/tix/${drv.exeName} \; -exec cp {} $out/share/hpc/vanilla/tix/${drv.exeName}/ \;
35
38
36 runHook postCheck
39 runHook postCheck
37 '';
40 '';
lib/cover-project.nixnew file+150
@@ -0,0 +1,150 @@
1+# A project coverage report is a composition of package coverage
2+# reports
3+{ stdenv, pkgs, lib, haskellLib }:
4+
5+# List of coverage reports to accumulate
6+coverageReports:
7+
8+let
9+ toBashArray = arr: "(" + (lib.concatStringsSep " " arr) + ")";
10+
11+ # Create table rows for a project coverage index page that look something like:
12+ #
13+ # | Package |
14+ # |------------------|
15+ # | cardano-shell |
16+ # | cardano-launcher |
17+ coverageTableRows = coverageReport:
18+ ''
19+ <tr>
20+ <td>
21+ <a href="${coverageReport.passthru.name}/hpc_index.html">${coverageReport.passthru.name}</href>
22+ </td>
23+ </tr>
24+ '';
25+
26+ projectIndexHtml = pkgs.writeText "index.html" ''
27+ <html>
28+ <head>
29+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
30+ </head>
31+ <body>
32+ <table border="1" width="100%">
33+ <tbody>
34+ <tr>
35+ <th>Report</th>
36+ </tr>
37+
38+ ${with lib; concatStringsSep "\n" (map coverageTableRows coverageReports)}
39+
40+ </tbody>
41+ </table>
42+ </body>
43+ </html>
44+ '';
45+
46+ ghc =
47+ if (builtins.length coverageReports) > 0
48+ then (builtins.head coverageReports).library.project.pkg-set.config.ghc.package or pkgs.ghc
49+ else pkgs.ghc;
50+
51+ libs = map (r: r.library) coverageReports;
52+
53+ projectLibs = map (pkg: pkg.components.library) (lib.attrValues (haskellLib.selectProjectPackages ((lib.head libs).project.hsPkgs)));
54+
55+ mixDirs =
56+ map
57+ (l: "${l}/share/hpc/vanilla/mix/${l.identifier.name}-${l.identifier.version}")
58+ (projectLibs);
59+
60+ srcDirs = map (l: l.src.outPath) (projectLibs);
61+
62+in pkgs.runCommand "project-coverage-report"
63+ ({ buildInputs = [ghc];
64+ LANG = "en_US.UTF-8";
65+ LC_ALL = "en_US.UTF-8";
66+ } // lib.optionalAttrs (stdenv.buildPlatform.libc == "glibc") {
67+ LOCALE_ARCHIVE = "${pkgs.buildPackages.glibcLocales}/lib/locale/locale-archive";
68+ })
69+ ''
70+ function markup() {
71+ local -n srcDs=$1
72+ local -n mixDs=$2
73+ local -n includedModules=$3
74+ local destDir=$4
75+ local tixFile=$5
76+
77+ local hpcMarkupCmd=("hpc" "markup" "--destdir=$destDir")
78+ for srcDir in "''${srcDs[@]}"; do
79+ hpcMarkupCmd+=("--srcdir=$srcDir")
80+ done
81+
82+ for mixDir in "''${mixDs[@]}"; do
83+ hpcMarkupCmd+=("--hpcdir=$mixDir")
84+ done
85+
86+ for module in "''${includedModules[@]}"; do
87+ hpcMarkupCmd+=("--include=$module")
88+ done
89+
90+ hpcMarkupCmd+=("$tixFile")
91+
92+ echo "''${hpcMarkupCmd[@]}"
93+ eval "''${hpcMarkupCmd[@]}"
94+ }
95+
96+ function findModules() {
97+ local searchDir=$2
98+ local pattern=$3
99+
100+ pushd $searchDir
101+ mapfile -d $'\0' $1 < <(find ./ -type f \
102+ -wholename "$pattern" -not -name "Paths*" \
103+ -exec basename {} \; \
104+ | sed "s/\.mix$//" \
105+ | tr "\n" "\0")
106+ popd
107+ }
108+
109+ mkdir -p $out/share/hpc/vanilla/tix/all
110+ mkdir -p $out/share/hpc/vanilla/mix/
111+ mkdir -p $out/share/hpc/vanilla/html/
112+
113+ # Find all tix files in each package
114+ tixFiles=()
115+ ${with lib; concatStringsSep "\n" (map (coverageReport: ''
116+ identifier="${coverageReport.name}"
117+ report=${coverageReport}
118+ tix="$report/share/hpc/vanilla/tix/$identifier/$identifier.tix"
119+ if test -f "$tix"; then
120+ tixFiles+=("$tix")
121+ fi
122+
123+ # Copy mix, tix, and html information over from each report
124+ cp -Rn $report/share/hpc/vanilla/mix/$identifier/* $out/share/hpc/vanilla/mix/
125+ cp -R $report/share/hpc/vanilla/tix/* $out/share/hpc/vanilla/tix/
126+ cp -R $report/share/hpc/vanilla/html/* $out/share/hpc/vanilla/html/
127+ '') coverageReports)}
128+
129+ if [ ''${#tixFiles[@]} -ne 0 ]; then
130+ # Create tix file with test run information for all packages
131+ tixFile="$out/share/hpc/vanilla/tix/all/all.tix"
132+ hpcSumCmd=("hpc" "sum" "--union" "--output=$tixFile")
133+ hpcSumCmd+=("''${tixFiles[@]}")
134+ echo "''${hpcSumCmd[@]}"
135+ eval "''${hpcSumCmd[@]}"
136+
137+ # Markup a HTML coverage report for the entire project
138+ cp ${projectIndexHtml} $out/share/hpc/vanilla/html/index.html
139+
140+ local markupOutDir="$out/share/hpc/vanilla/html/all"
141+ local srcDirs=${toBashArray srcDirs}
142+ local mixDirs=${toBashArray mixDirs}
143+ local allMixModules=()
144+
145+ mkdir $markupOutDir
146+ findModules allMixModules "$out/share/hpc/vanilla/mix/" "*.mix"
147+
148+ markup srcDirs mixDirs allMixModules "$markupOutDir" "$tixFile"
149+ fi
150+ ''
lib/cover.nixnew file+165
@@ -0,0 +1,165 @@
1+{ stdenv, lib, haskellLib, pkgs }:
2+
3+# Name of the coverage report, which should be unique
4+{ 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
16+}:
17+
18+let
19+ toBashArray = arr: "(" + (lib.concatStringsSep " " arr) + ")";
20+
21+ mixDir = l: "${l}/share/hpc/vanilla/mix/${l.identifier.name}-${l.identifier.version}";
22+ mixDirs = map mixDir mixLibraries;
23+
24+ srcDirs = map (l: l.src.outPath) mixLibraries;
25+
26+in pkgs.runCommand (name + "-coverage-report")
27+ ({ buildInputs = [ ghc ];
28+ passthru = {
29+ inherit name library checks;
30+ };
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
33+ # don't know what version of HPC we will be using, hence we should
34+ # always use the workaround.
35+ # https://gitlab.haskell.org/ghc/ghc/-/issues/17073
36+ LANG = "en_US.UTF-8";
37+ LC_ALL = "en_US.UTF-8";
38+ } // lib.optionalAttrs (stdenv.buildPlatform.libc == "glibc") {
39+ LOCALE_ARCHIVE = "${pkgs.buildPackages.glibcLocales}/lib/locale/locale-archive";
40+ })
41+ ''
42+ function markup() {
43+ local -n srcDs=$1
44+ local -n mixDs=$2
45+ local -n includedModules=$3
46+ local destDir=$4
47+ local tixFile=$5
48+
49+ local hpcMarkupCmd=("hpc" "markup" "--destdir=$destDir")
50+ for srcDir in "''${srcDs[@]}"; do
51+ hpcMarkupCmd+=("--srcdir=$srcDir")
52+ done
53+
54+ for mixDir in "''${mixDs[@]}"; do
55+ hpcMarkupCmd+=("--hpcdir=$mixDir")
56+ done
57+
58+ for module in "''${includedModules[@]}"; do
59+ hpcMarkupCmd+=("--include=$module")
60+ done
61+
62+ hpcMarkupCmd+=("$tixFile")
63+
64+ echo "''${hpcMarkupCmd[@]}"
65+ eval "''${hpcMarkupCmd[@]}"
66+ }
67+
68+ function sumTix() {
69+ local -n includedModules=$1
70+ local -n tixFs=$2
71+ local outFile="$3"
72+
73+ local hpcSumCmd=("hpc" "sum" "--union" "--output=$outFile")
74+
75+ for module in "''${includedModules[@]}"; do
76+ hpcSumCmd+=("--include=$module")
77+ done
78+
79+ for tixFile in "''${tixFs[@]}"; do
80+ hpcSumCmd+=("$tixFile")
81+ done
82+
83+ echo "''${hpcSumCmd[@]}"
84+ eval "''${hpcSumCmd[@]}"
85+ }
86+
87+ function findModules() {
88+ local searchDir=$2
89+ local pattern=$3
90+
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
98+ }
99+
100+ local mixDirs=${toBashArray mixDirs}
101+
102+ mkdir -p $out/share/hpc/vanilla/mix/${name}
103+ mkdir -p $out/share/hpc/vanilla/tix/${name}
104+ mkdir -p $out/share/hpc/vanilla/html/${name}
105+
106+ # Copy over mix files verbatim
107+ for dir in "''${mixDirs[@]}"; do
108+ if [ -d "$dir" ]; then
109+ cp -R "$dir"/* $out/share/hpc/vanilla/mix/${name}
110+ fi
111+ done
112+
113+ local srcDirs=${toBashArray srcDirs}
114+ local allMixModules=()
115+ local pkgMixModules=()
116+
117+ # The behaviour of stack coverage reports is to provide tix files
118+ # that include coverage information for every local package, but
119+ # to provide HTML reports that only include coverage info for the
120+ # current package. We emulate the same behaviour here. If the user
121+ # includes all local packages in the mix libraries argument, they
122+ # will get a coverage report very similar to stack.
123+
124+ # All mix modules
125+ findModules allMixModules "$out/share/hpc/vanilla/mix/${name}" "*.mix"
126+ # Only mix modules corresponding to this package
127+ findModules pkgMixModules "$out/share/hpc/vanilla/mix/${name}" "*${name}*/*.mix"
128+
129+ # For each test
130+ local tixFiles=()
131+ ${lib.concatStringsSep "\n" (builtins.map (check: ''
132+ if [ -d "${check}/share/hpc/vanilla/tix" ]; then
133+ pushd ${check}/share/hpc/vanilla/tix
134+
135+ tixFile="$(find . -iwholename "*.tix" -type f -print -quit)"
136+ local newTixFile=$out/share/hpc/vanilla/tix/${name}/"$tixFile"
137+
138+ mkdir -p "$(dirname $newTixFile)"
139+ # Copy over the tix file verbatim
140+ cp "$tixFile" "$newTixFile"
141+
142+ # Add the tix file to our list
143+ tixFiles+=("$newTixFile")
144+
145+ # Create a coverage report for *just that test*
146+ markup srcDirs mixDirs pkgMixModules "$out/share/hpc/vanilla/html/${name}/${check.exeName}/" "$newTixFile"
147+
148+ popd
149+ fi
150+ '') checks)
151+ }
152+
153+ # Sum tix files to create a tix file with all relevant tix
154+ # information and markup a HTML report from this info.
155+ if (( "''${#tixFiles[@]}" > 0 )); then
156+ local sumTixFile="$out/share/hpc/vanilla/tix/${name}/${name}.tix"
157+ local markupOutDir="$out/share/hpc/vanilla/html/${name}"
158+
159+ # Sum all of our tix file, including modules from any local package
160+ sumTix allMixModules tixFiles "$sumTixFile"
161+
162+ # Markup a HTML report, included modules from only this package
163+ markup srcDirs mixDirs pkgMixModules "$markupOutDir" "$sumTixFile"
164+ fi
165+ ''
lib/default.nix+10
@@ -232,6 +232,16 @@ in {
232 inherit stdenv lib haskellLib srcOnly;
232 inherit stdenv lib haskellLib srcOnly;
233 };
233 };
234
234
235+ # Do coverage of a package
236+ coverageReport = import ./cover.nix {
237+ inherit stdenv lib haskellLib pkgs;
238+ };
239+
240+ # Do coverage of a project
241+ projectCoverageReport = import ./cover-project.nix {
242+ inherit stdenv lib haskellLib pkgs;
243+ };
244+
235 # Use `isCrossHost` to identify when we are cross compiling and
245 # Use `isCrossHost` to identify when we are cross compiling and
236 # the code we are producing will not run on the build system
246 # the code we are producing will not run on the build system
237 # without an emulator.
247 # without an emulator.
mkdocs.yml+2
@@ -35,6 +35,7 @@ pages:
35 - 'Bumping Hackage and Stackage snapshots': tutorials/hackage-stackage.md
35 - 'Bumping Hackage and Stackage snapshots': tutorials/hackage-stackage.md
36 - 'Materialization: Speeding up Nix evaluation': tutorials/materialization.md
36 - 'Materialization: Speeding up Nix evaluation': tutorials/materialization.md
37 - 'Cross-compiling your project': tutorials/cross-compilation.md
37 - 'Cross-compiling your project': tutorials/cross-compilation.md
38+ - 'Generating coverage information': tutorials/coverage.md
38 - 'Reference':
39 - 'Reference':
39 - 'Suported GHC versions': reference/supported-ghc-versions.md
40 - 'Suported GHC versions': reference/supported-ghc-versions.md
40 - 'Command-line tools': reference/commands.md
41 - 'Command-line tools': reference/commands.md
@@ -52,4 +53,5 @@ pages:
52 - 'Removing withPackage wrapper': dev/removing-with-package-wrapper.md
53 - 'Removing withPackage wrapper': dev/removing-with-package-wrapper.md
53 - 'Test Suite': dev/tests.md
54 - 'Test Suite': dev/tests.md
54 - 'Adding a new GHC version': dev/adding-new-ghc.md
55 - 'Adding a new GHC version': dev/adding-new-ghc.md
56+ - 'Coverage': dev/coverage.md
55 - 'ChangeLog': changelog.md
57 - 'ChangeLog': changelog.md
modules/plan.nix+5
@@ -108,6 +108,11 @@ let
108 type = bool;
108 type = bool;
109 default = (def.doQuickjump or true);
109 default = (def.doQuickjump or true);
110 };
110 };
111+ doCoverage = mkOption {
112+ description = "Enable production of test coverage reports.";
113+ type = bool;
114+ default = (def.doCoverage or false);
115+ };
111 dontPatchELF = mkOption {
116 dontPatchELF = mkOption {
112 description = "If set, the patchelf command is not used to remove unnecessary RPATH entries. Only applies to Linux.";
117 description = "If set, the patchelf command is not used to remove unnecessary RPATH entries. Only applies to Linux.";
113 type = bool;
118 type = bool;
overlays/haskell.nix+35−21
@@ -482,15 +482,18 @@ final: prev: {
482 { compiler.nix-name = args.compiler-nix-name; };
482 { compiler.nix-name = args.compiler-nix-name; };
483 extra-hackages = args.extra-hackages or [];
483 extra-hackages = args.extra-hackages or [];
484 };
484 };
485+
485− in addProjectAndPackageAttrs {
486+ project = addProjectAndPackageAttrs rec {
486− inherit (pkg-set.config) hsPkgs;
487+ inherit (pkg-set.config) hsPkgs;
487− inherit pkg-set;
488+ inherit pkg-set;
488− plan-nix = callProjectResults.projectNix;
489+ plan-nix = callProjectResults.projectNix;
489− inherit (callProjectResults) index-state;
490+ inherit (callProjectResults) index-state;
490− tool = final.buildPackages.haskell-nix.tool pkg-set.config.compiler.nix-name;
491+ tool = final.buildPackages.haskell-nix.tool pkg-set.config.compiler.nix-name;
491− tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
492+ tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
492− roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
493+ roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
493− };
494+ };
495+ in project;
496+
494
497
495 # Take `hsPkgs` from the `rawProject` and update all the packages and
498 # Take `hsPkgs` from the `rawProject` and update all the packages and
496 # components so they have a `.project` attribute and as well as
499 # components so they have a `.project` attribute and as well as
@@ -498,25 +501,34 @@ final: prev: {
498 addProjectAndPackageAttrs = rawProject:
501 addProjectAndPackageAttrs = rawProject:
499 final.lib.fix (project':
502 final.lib.fix (project':
500 let project = project' // { recurseForDerivations = false; };
503 let project = project' // { recurseForDerivations = false; };
501− in rawProject // {
504+ in rawProject // rec {
502 hsPkgs = (final.lib.mapAttrs (n: package':
505 hsPkgs = (final.lib.mapAttrs (n: package':
503 if package' == null
506 if package' == null
504 then null
507 then null
505 else
508 else
506 let package = package' // { recurseForDerivations = false; };
509 let package = package' // { recurseForDerivations = false; };
507− in package' // {
510+ in package' // rec {
508 components = final.lib.mapAttrs (n: v:
511 components = final.lib.mapAttrs (n: v:
509 if n == "library" || n == "all"
512 if n == "library" || n == "all"
510 then v // { inherit project package; }
513 then v // { inherit project package; }
511 else final.lib.mapAttrs (_: c: c // { inherit project package; }) v
514 else final.lib.mapAttrs (_: c: c // { inherit project package; }) v
512 ) package'.components;
515 ) package'.components;
513 inherit project;
516 inherit project;
517+
518+ coverageReport = haskellLib.coverageReport (rec {
519+ name = package.identifier.name + "-" + package.identifier.version;
520+ inherit (components) library;
521+ checks = final.lib.filter (final.lib.isDerivation) (final.lib.attrValues package'.checks);
522+ mixLibraries = map (pkg: pkg.components.library) (final.lib.attrValues (haskellLib.selectProjectPackages project.hsPkgs));
523+ });
514 }
524 }
515 ) rawProject.hsPkgs
525 ) rawProject.hsPkgs
516 // {
526 // {
517 # These are functions not packages
527 # These are functions not packages
518 inherit (rawProject.hsPkgs) shellFor ghcWithHoogle ghcWithPackages;
528 inherit (rawProject.hsPkgs) shellFor ghcWithHoogle ghcWithPackages;
519 });
529 });
530+
531+ projectCoverageReport = haskellLib.projectCoverageReport (map (pkg: pkg.coverageReport) (final.lib.attrValues (haskellLib.selectProjectPackages hsPkgs)));
520 });
532 });
521
533
522 cabalProject =
534 cabalProject =
@@ -525,7 +537,7 @@ final: prev: {
525 args = { caller = "hackage-package"; } // args';
537 args = { caller = "hackage-package"; } // args';
526 p = cabalProject' args;
538 p = cabalProject' args;
527 in p.hsPkgs // {
539 in p.hsPkgs // {
528− inherit (p) plan-nix index-state tool tools roots;
540+ inherit (p) plan-nix index-state tool tools roots projectCoverageReport;
529 # Provide `nix-shell -A shells.ghc` for users migrating from the reflex-platform.
541 # Provide `nix-shell -A shells.ghc` for users migrating from the reflex-platform.
530 # But we should encourage use of `nix-shell -A shellFor`
542 # But we should encourage use of `nix-shell -A shellFor`
531 shells.ghc = p.hsPkgs.shellFor {};
543 shells.ghc = p.hsPkgs.shellFor {};
@@ -543,18 +555,20 @@ final: prev: {
543 ++ (args.modules or [])
555 ++ (args.modules or [])
544 ++ final.lib.optional (args ? ghc) { ghc.package = args.ghc; };
556 ++ final.lib.optional (args ? ghc) { ghc.package = args.ghc; };
545 };
557 };
558+
546− in addProjectAndPackageAttrs {
559+ project = addProjectAndPackageAttrs {
547− inherit (pkg-set.config) hsPkgs;
560+ inherit (pkg-set.config) hsPkgs;
548− inherit pkg-set;
561+ inherit pkg-set;
549− stack-nix = callProjectResults.projectNix;
562+ stack-nix = callProjectResults.projectNix;
550− tool = final.buildPackages.haskell-nix.tool pkg-set.config.compiler.nix-name;
563+ tool = final.buildPackages.haskell-nix.tool pkg-set.config.compiler.nix-name;
551− tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
564+ tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
552− roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
565+ roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
553− };
566+ };
567+ in project;
554
568
555 stackProject = args: let p = stackProject' args;
569 stackProject = args: let p = stackProject' args;
556 in p.hsPkgs // {
570 in p.hsPkgs // {
557− inherit (p) stack-nix tool tools roots;
571+ inherit (p) stack-nix tool tools roots projectCoverageReport;
558 # Provide `nix-shell -A shells.ghc` for users migrating from the reflex-platform.
572 # Provide `nix-shell -A shells.ghc` for users migrating from the reflex-platform.
559 # But we should encourage use of `nix-shell -A shellFor`
573 # But we should encourage use of `nix-shell -A shellFor`
560 shells.ghc = p.hsPkgs.shellFor {};
574 shells.ghc = p.hsPkgs.shellFor {};
test/buildable/default.nix+1−1
@@ -21,7 +21,7 @@ in recurseIntoAttrs {
21 buildCommand =
21 buildCommand =
22 (concatStrings (mapAttrsToList (name: value: ''
22 (concatStrings (mapAttrsToList (name: value: ''
23 printf "checking whether executable runs... " >& 2
23 printf "checking whether executable runs... " >& 2
24− cat ${haskellLib.check value}
24+ cat ${haskellLib.check value}/test-stdout
25 '') packages.buildable-test.components.exes)) + ''
25 '') packages.buildable-test.components.exes)) + ''
26 touch $out
26 touch $out
27 '';
27 '';
test/cabal-22/default.nix+3−3
@@ -26,7 +26,7 @@ in recurseIntoAttrs {
26
26
27 # fixme: run on target platform when cross-compiled
27 # fixme: run on target platform when cross-compiled
28 printf "checking whether executable runs... " >& 2
28 printf "checking whether executable runs... " >& 2
29− cat ${haskellLib.check packages.project.components.exes.project}
29+ cat ${haskellLib.check packages.project.components.exes.project}/test-stdout
30
30
31 '' +
31 '' +
32 # Aarch is statically linked and does not produce a .so file.
32 # Aarch is statically linked and does not produce a .so file.
@@ -56,10 +56,10 @@ in recurseIntoAttrs {
56 touch $out
56 touch $out
57
57
58 printf "checking whether benchmark ran... " >& 2
58 printf "checking whether benchmark ran... " >& 2
59− cat ${haskellLib.check packages.project.components.benchmarks.project-bench}
59+ cat ${haskellLib.check packages.project.components.benchmarks.project-bench}/test-stdout
60
60
61 printf "checking whether tests ran... " >& 2
61 printf "checking whether tests ran... " >& 2
62− cat ${haskellLib.check packages.project.components.tests.unit}
62+ cat ${haskellLib.check packages.project.components.tests.unit}/test-stdout
63 '';
63 '';
64
64
65 meta.platforms = platforms.all;
65 meta.platforms = platforms.all;
test/cabal-hpack/default.nix+1−1
@@ -36,7 +36,7 @@ in recurseIntoAttrs {
36
36
37 # fixme: run on target platform when cross-compiled
37 # fixme: run on target platform when cross-compiled
38 printf "checking whether executable runs... " >& 2
38 printf "checking whether executable runs... " >& 2
39− cat ${haskellLib.check packages.cabal-hpack.components.exes.cabal-hpack}
39+ cat ${haskellLib.check packages.cabal-hpack.components.exes.cabal-hpack}/test-stdout
40 '' + (if stdenv.hostPlatform.isMusl
40 '' + (if stdenv.hostPlatform.isMusl
41 then ''
41 then ''
42 printf "checking that executable is statically linked... " >& 2
42 printf "checking that executable is statically linked... " >& 2
test/cabal-simple/default.nix+1−1
@@ -39,7 +39,7 @@ in recurseIntoAttrs {
39
39
40 # fixme: run on target platform when cross-compiled
40 # fixme: run on target platform when cross-compiled
41 printf "checking whether executable runs... " >& 2
41 printf "checking whether executable runs... " >& 2
42− cat ${haskellLib.check packages.cabal-simple.components.exes.cabal-simple}
42+ cat ${haskellLib.check packages.cabal-simple.components.exes.cabal-simple}/test-stdout
43 '' + (if stdenv.hostPlatform.isMusl
43 '' + (if stdenv.hostPlatform.isMusl
44 then ''
44 then ''
45 printf "checking that executable is statically linked... " >& 2
45 printf "checking that executable is statically linked... " >& 2
test/cabal-source-repo-comments/default.nix+1−1
@@ -20,7 +20,7 @@ in recurseIntoAttrs {
20 exe="${packages.use-cabal-simple.components.exes.use-cabal-simple}/bin/use-cabal-simple${stdenv.hostPlatform.extensions.executable}"
20 exe="${packages.use-cabal-simple.components.exes.use-cabal-simple}/bin/use-cabal-simple${stdenv.hostPlatform.extensions.executable}"
21
21
22 printf "checking whether executable runs... " >& 2
22 printf "checking whether executable runs... " >& 2
23− cat ${haskellLib.check packages.use-cabal-simple.components.exes.use-cabal-simple}
23+ cat ${haskellLib.check packages.use-cabal-simple.components.exes.use-cabal-simple}/test-stdout
24
24
25 touch $out
25 touch $out
26 '';
26 '';
test/cabal-source-repo/default.nix+1−1
@@ -20,7 +20,7 @@ in recurseIntoAttrs {
20 exe="${packages.use-cabal-simple.components.exes.use-cabal-simple}/bin/use-cabal-simple${stdenv.hostPlatform.extensions.executable}"
20 exe="${packages.use-cabal-simple.components.exes.use-cabal-simple}/bin/use-cabal-simple${stdenv.hostPlatform.extensions.executable}"
21
21
22 printf "checking whether executable runs... " >& 2
22 printf "checking whether executable runs... " >& 2
23− cat ${haskellLib.check packages.use-cabal-simple.components.exes.use-cabal-simple}
23+ cat ${haskellLib.check packages.use-cabal-simple.components.exes.use-cabal-simple}/test-stdout
24
24
25 touch $out
25 touch $out
26 '';
26 '';
test/cabal-sublib/default.nix+1−1
@@ -36,7 +36,7 @@ in recurseIntoAttrs {
36
36
37 # fixme: run on target platform when cross-compiled
37 # fixme: run on target platform when cross-compiled
38 printf "checking whether executable runs... " >& 2
38 printf "checking whether executable runs... " >& 2
39− cat ${haskellLib.check packages.cabal-sublib.components.exes.cabal-sublib}
39+ cat ${haskellLib.check packages.cabal-sublib.components.exes.cabal-sublib}/test-stdout
40
40
41 '' +
41 '' +
42 # Musl and Aarch are statically linked..
42 # Musl and Aarch are statically linked..
test/call-cabal-project-to-nix/default.nix+1−1
@@ -29,7 +29,7 @@ in recurseIntoAttrs {
29 exe="${packages.cabal-simple.components.exes.cabal-simple}/bin/cabal-simple${stdenv.hostPlatform.extensions.executable}"
29 exe="${packages.cabal-simple.components.exes.cabal-simple}/bin/cabal-simple${stdenv.hostPlatform.extensions.executable}"
30
30
31 printf "checking whether executable runs... " >& 2
31 printf "checking whether executable runs... " >& 2
32− cat ${haskellLib.check packages.cabal-simple.components.exes.cabal-simple}
32+ cat ${haskellLib.check packages.cabal-simple.components.exes.cabal-simple}/test-stdout
33
33
34 touch $out
34 touch $out
35 '';
35 '';
test/call-stack-to-nix/default.nix+1−1
@@ -24,7 +24,7 @@ in recurseIntoAttrs {
24 exe="${packages.stack-simple.components.exes.stack-simple-exe}/bin/stack-simple-exe${stdenv.hostPlatform.extensions.executable}"
24 exe="${packages.stack-simple.components.exes.stack-simple-exe}/bin/stack-simple-exe${stdenv.hostPlatform.extensions.executable}"
25
25
26 printf "checking whether executable runs... " >& 2
26 printf "checking whether executable runs... " >& 2
27− cat ${haskellLib.check packages.stack-simple.components.exes.stack-simple-exe}
27+ cat ${haskellLib.check packages.stack-simple.components.exes.stack-simple-exe}/test-stdout
28
28
29 touch $out
29 touch $out
30 '';
30 '';
test/coverage/cabal.projectnew file+2
@@ -0,0 +1,2 @@
1+packages: pkga
2+ pkgb
test/coverage/conduit.hsnew file+19
@@ -0,0 +1,19 @@
1+-- https://github.com/snoyberg/conduit#readme
2+
3+import Conduit
4+import System.Directory (removeFile)
5+
6+main = do
7+ -- Pure operations: summing numbers.
8+ print $ runConduitPure $ yieldMany [1..10] .| sumC
9+
10+ -- Exception safe file access: copy a file.
11+ writeFile "input.txt" "This is a test." -- create the source file
12+ runConduitRes $ sourceFileBS "input.txt" .| sinkFile "output.txt" -- actual copying
13+ readFile "output.txt" >>= putStrLn -- prove that it worked
14+
15+ -- Perform transformations.
16+ print $ runConduitPure $ yieldMany [1..10] .| mapC (+ 1) .| sinkList
17+
18+ removeFile "input.txt"
19+ removeFile "output.txt"
test/coverage/default.nixnew file+127
@@ -0,0 +1,127 @@
1+{ stdenv, cabal-install, cabalProject', stackProject', recurseIntoAttrs, runCommand, testSrc, compiler-nix-name }:
2+
3+with stdenv.lib;
4+
5+let
6+ projectArgs = {
7+ src = testSrc "coverage";
8+ inherit compiler-nix-name;
9+ modules = [{
10+ # Package has no exposed modules which causes
11+ # haddock: No input file(s)
12+ packages.bytestring-builder.doHaddock = false;
13+
14+ # Coverage
15+ packages.pkga.components.library.doCoverage = true;
16+ packages.pkgb.components.library.doCoverage = true;
17+ }];
18+ };
19+
20+ cabalProj = (cabalProject' projectArgs);
21+ stackProj = (stackProject' projectArgs);
22+
23+in recurseIntoAttrs ({
24+ run = stdenv.mkDerivation {
25+ name = "coverage-test";
26+
27+ buildCommand = ''
28+ ########################################################################
29+ # test coverage reports with an example project
30+
31+ fileExistsNonEmpty() {
32+ local file=$1
33+ if [ ! -f "$file" ]; then
34+ echo "Missing: $file"
35+ exit 1
36+ fi
37+ local filesize=$(command stat --format '%s' "$file")
38+ if [ $filesize -eq 0 ]; then
39+ echo "File must not be empty: $file"
40+ exit 1
41+ fi
42+ }
43+ findFileExistsNonEmpty() {
44+ local searchDir=$1
45+ local filePattern=$2
46+
47+ local file="$(find $searchDir -name $filePattern -print -quit)"
48+
49+ if [ -z $file ]; then
50+ echo "Couldn't find file \"$filePattern\" in directory \"$searchDir\"."
51+ exit 1
52+ fi
53+
54+ local filesize=$(command stat --format '%s' "$file")
55+ if [ $filesize -eq 0 ]; then
56+ echo "File must not be empty: $file"
57+ exit 1
58+ fi
59+ }
60+ dirExistsEmpty() {
61+ local dir=$1
62+ if [ ! -d "$dir" ]; then
63+ echo "Missing: $dir"
64+ exit 1
65+ fi
66+ if [ "$(ls -A $dir)" ]; then
67+ echo "Dir should be empty: $dir"
68+ exit 1
69+ fi
70+ }
71+ dirExists() {
72+ local dir=$1
73+ if [ ! -d "$dir" ]; then
74+ echo "Missing: $dir"
75+ exit 1
76+ fi
77+ }
78+
79+ ${concatStringsSep "\n" (map (project: ''
80+ pkga_basedir="${project.hsPkgs.pkga.coverageReport}/share/hpc/vanilla"
81+ findFileExistsNonEmpty "$pkga_basedir/mix/pkga-0.1.0.0/" "PkgA.mix"
82+ dirExistsEmpty "$pkga_basedir/tix/pkga-0.1.0.0"
83+ dirExistsEmpty "$pkga_basedir/html/pkga-0.1.0.0"
84+
85+ pkgb_basedir="${project.hsPkgs.pkgb.coverageReport}/share/hpc/vanilla"
86+ testTix="$pkgb_basedir/tix/pkgb-0.1.0.0/tests/tests.tix"
87+ libTix="$pkgb_basedir/tix/pkgb-0.1.0.0/pkgb-0.1.0.0.tix"
88+ fileExistsNonEmpty "$testTix"
89+ fileExistsNonEmpty "$libTix"
90+ findFileExistsNonEmpty "$pkgb_basedir/mix/pkgb-0.1.0.0/" "ConduitExample.mix"
91+ findFileExistsNonEmpty "$pkgb_basedir/mix/pkgb-0.1.0.0/" "PkgB.mix"
92+ fileExistsNonEmpty "$pkgb_basedir/html/pkgb-0.1.0.0/hpc_index.html"
93+
94+ filesizeTestsTix=$(command stat --format '%s' "$testTix")
95+ filesizeLibTix=$(command stat --format '%s' "$libTix")
96+ if (( filesizeTestsTix <= filesizeLibTix )); then
97+ echo "Filesize of \"$testTix\" ($filesizeTestsTix) should be greather than that of \"$libTix\" ($filesizeLibTix). Did you forget to exclude test modules when creating \"$libTix\"?"
98+ exit 1
99+ fi
100+
101+ project_basedir="${project.projectCoverageReport}/share/hpc/vanilla"
102+ fileExistsNonEmpty "$project_basedir/html/index.html"
103+ dirExists "$project_basedir/html/pkga-0.1.0.0"
104+ dirExists "$project_basedir/html/pkgb-0.1.0.0"
105+ findFileExistsNonEmpty "$project_basedir/mix/" "PkgA.mix"
106+ findFileExistsNonEmpty "$project_basedir/mix/" "PkgB.mix"
107+ findFileExistsNonEmpty "$project_basedir/mix/" "ConduitExample.mix"
108+ dirExists "$project_basedir/tix/all"
109+ fileExistsNonEmpty "$project_basedir/tix/all/all.tix"
110+ dirExists "$project_basedir/tix/pkga-0.1.0.0"
111+ dirExists "$project_basedir/tix/pkgb-0.1.0.0"
112+ fileExistsNonEmpty "$project_basedir/tix/pkgb-0.1.0.0/pkgb-0.1.0.0.tix"
113+ dirExists "$project_basedir/tix/pkgb-0.1.0.0/tests"
114+ fileExistsNonEmpty "$project_basedir/tix/pkgb-0.1.0.0/tests/tests.tix"
115+ '') [ cabalProj stackProj ])}
116+
117+ touch $out
118+ '';
119+
120+ meta.platforms = platforms.all;
121+
122+ passthru = {
123+ # Used for debugging with nix repl
124+ inherit cabalProj stackProj;
125+ };
126+ };
127+})
test/coverage/pkga/MainA.hsnew file+4
@@ -0,0 +1,4 @@
1+module Main where
2+
3+main :: IO ()
4+main = putStrLn "This is MainA"
test/coverage/pkga/PkgA.hsnew file+16
@@ -0,0 +1,16 @@
1+module PkgA (decode) where
2+
3+import Control.Lens
4+import Data.Text.Lens
5+import Data.Char
6+import Data.Text (Text)
7+
8+decode :: Text -> Text
9+decode = unpacked . mapped %~ rot 13
10+
11+rot :: Int -> Char -> Char
12+rot n c | c >= 'a' && c <= 'z' = r 'a' 'z'
13+ | c >= 'A' && c <= 'Z' = r 'A' 'Z'
14+ | otherwise = c
15+ where
16+ r a b = chr $ ord a + ((ord c - ord a + n) `mod` (ord b - ord a + 1))
test/coverage/pkga/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
test/coverage/pkga/pkga.cabalnew file+26
@@ -0,0 +1,26 @@
1+cabal-version: 2.2
2+-- Initial package description 'pkga.cabal' generated by 'cabal init'. For
3+-- further documentation, see http://haskell.org/cabal/users-guide/
4+
5+name: pkga
6+version: 0.1.0.0
7+-- synopsis:
8+-- description:
9+-- bug-reports:
10+license: LicenseRef-PublicDomain
11+author: Rodney Lorrimar
12+maintainer: rodney.lorrimar@iohk.io
13+category: Testing
14+
15+library
16+ exposed-modules: PkgA
17+ build-depends: base
18+ , lens
19+ , text
20+ default-language: Haskell2010
21+
22+executable pkga-exe
23+ main-is: MainA.hs
24+ build-depends: base
25+ hs-source-dirs: .
26+ default-language: Haskell2010
test/coverage/pkgb/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
test/coverage/pkgb/app/Main.hsnew file+10
@@ -0,0 +1,10 @@
1+module Main where
2+
3+import ConduitExample (example)
4+import PkgB (message)
5+import qualified Data.Text.IO as T
6+
7+main :: IO ()
8+main = do
9+ T.putStrLn message
10+ example
test/coverage/pkgb/app/tests.hsnew file+7
@@ -0,0 +1,7 @@
1+module Main where
2+
3+import System.Process
4+import ConduitExample
5+
6+main :: IO ()
7+main = example
test/coverage/pkgb/pkgb.cabalnew file+43
@@ -0,0 +1,43 @@
1+cabal-version: 2.2
2+-- Initial package description 'pkgb.cabal' generated by 'cabal init'. For
3+-- further documentation, see http://haskell.org/cabal/users-guide/
4+
5+name: pkgb
6+version: 0.1.0.0
7+-- synopsis:
8+-- description:
9+-- bug-reports:
10+license: LicenseRef-PublicDomain
11+author: Rodney Lorrimar
12+maintainer: rodney.lorrimar@iohk.io
13+category: Testing
14+
15+library
16+ exposed-modules: ConduitExample
17+ , PkgB
18+ build-depends: base
19+ , pkga
20+ , conduit
21+ , conduit-extra
22+ , directory
23+ , resourcet
24+ hs-source-dirs: src
25+ default-language: Haskell2010
26+
27+executable pkgb
28+ main-is: Main.hs
29+ build-depends: base
30+ , pkgb
31+ , optparse-applicative
32+ , text
33+ hs-source-dirs: app
34+ default-language: Haskell2010
35+
36+test-suite tests
37+ type: exitcode-stdio-1.0
38+ main-is: tests.hs
39+ hs-source-dirs: app
40+ build-depends: base
41+ , pkgb
42+ , process
43+ build-tools: pkga
test/coverage/pkgb/src/ConduitExample.hsnew file+21
@@ -0,0 +1,21 @@
1+-- https://github.com/snoyberg/conduit#readme
2+
3+module ConduitExample (example) where
4+
5+import Conduit
6+import System.Directory (removeFile)
7+
8+example = do
9+ -- Pure operations: summing numbers.
10+ print $ runConduitPure $ yieldMany [1..10] .| sumC
11+
12+ -- Exception safe file access: copy a file.
13+ writeFile "input.txt" "This is a test." -- create the source file
14+ runConduitRes $ sourceFileBS "input.txt" .| sinkFile "output.txt" -- actual copying
15+ readFile "output.txt" >>= putStrLn -- prove that it worked
16+
17+ -- Perform transformations.
18+ print $ runConduitPure $ yieldMany [1..10] .| mapC (+ 1) .| sinkList
19+
20+ removeFile "input.txt"
21+ removeFile "output.txt"
test/coverage/pkgb/src/PkgB.hsnew file+7
@@ -0,0 +1,7 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module PkgB (message) where
4+
5+import PkgA (decode)
6+
7+message = decode "Guvf vf n pnony cebwrpg!"
test/coverage/pkgb/src/conduit-test.hsnew file+5
@@ -0,0 +1,5 @@
1+module Main where
2+
3+import ConduitExample
4+
5+main = example
test/coverage/stack.yamlnew file+5
@@ -0,0 +1,5 @@
1+resolver: lts-14.13
2+
3+packages:
4+- pkga/
5+- pkgb/
test/default.nix+1
@@ -181,6 +181,7 @@ let
181 hls-stack = callTest ./haskell-language-server/stack.nix { inherit compiler-nix-name; };
181 hls-stack = callTest ./haskell-language-server/stack.nix { inherit compiler-nix-name; };
182 cabal-hpack = callTest ./cabal-hpack { inherit util compiler-nix-name; };
182 cabal-hpack = callTest ./cabal-hpack { inherit util compiler-nix-name; };
183 index-state = callTest ./index-state { inherit compiler-nix-name; };
183 index-state = callTest ./index-state { inherit compiler-nix-name; };
184+ coverage = callTest ./coverage { inherit compiler-nix-name; };
184
185
185 unit = unitTests;
186 unit = unitTests;
186 } // lib.optionalAttrs (!stdenv.hostPlatform.isGhcjs && compiler-nix-name != "ghc8101" && compiler-nix-name != "ghc8102" ) {
187 } // lib.optionalAttrs (!stdenv.hostPlatform.isGhcjs && compiler-nix-name != "ghc8101" && compiler-nix-name != "ghc8102" ) {
test/exe-only/default.nix+1−1
@@ -26,7 +26,7 @@ in recurseIntoAttrs {
26
26
27 # fixme: run on target platform when cross-compiled
27 # fixme: run on target platform when cross-compiled
28 printf "checking whether executable ran... " >& 2
28 printf "checking whether executable ran... " >& 2
29− cat ${haskellLib.check packages.exe-only.components.exes.exe-only}
29+ cat ${haskellLib.check packages.exe-only.components.exes.exe-only}/test-stdout
30 '' +
30 '' +
31 # Aarch are statically linked and does not have ldd for these tests.
31 # Aarch are statically linked and does not have ldd for these tests.
32 optionalString (!stdenv.hostPlatform.isAarch32 && !stdenv.hostPlatform.isAarch64) (
32 optionalString (!stdenv.hostPlatform.isAarch32 && !stdenv.hostPlatform.isAarch64) (
test/extra-hackage/default.nix+1−1
@@ -41,7 +41,7 @@ in recurseIntoAttrs {
41 printf "size of executable $exe is $size. \n" >& 2
41 printf "size of executable $exe is $size. \n" >& 2
42 # fixme: run on target platform when cross-compiled
42 # fixme: run on target platform when cross-compiled
43 printf "checking whether executable runs... " >& 2
43 printf "checking whether executable runs... " >& 2
44− cat ${haskellLib.check packages.external-package-user.components.exes.external-package-user}
44+ cat ${haskellLib.check packages.external-package-user.components.exes.external-package-user}/test-stdout
45 '' + (if stdenv.hostPlatform.isMusl
45 '' + (if stdenv.hostPlatform.isMusl
46 then ''
46 then ''
47 printf "checking that executable is statically linked... " >& 2
47 printf "checking that executable is statically linked... " >& 2
test/ghc-options/cabal.nix+1−1
@@ -21,7 +21,7 @@ in recurseIntoAttrs {
21
21
22 buildCommand = ''
22 buildCommand = ''
23 printf "checking whether executable runs... " >& 2
23 printf "checking whether executable runs... " >& 2
24− cat ${haskellLib.check packages.test-ghc-options.components.exes.test-ghc-options-exe}
24+ cat ${haskellLib.check packages.test-ghc-options.components.exes.test-ghc-options-exe}/test-stdout
25
25
26 touch $out
26 touch $out
27 '';
27 '';
test/ghc-options/stack.nix+1−1
@@ -22,7 +22,7 @@ in recurseIntoAttrs {
22
22
23 buildCommand = ''
23 buildCommand = ''
24 printf "checking whether executable runs... " >& 2
24 printf "checking whether executable runs... " >& 2
25− cat ${haskellLib.check packages.test-ghc-options.components.exes.test-ghc-options-exe}
25+ cat ${haskellLib.check packages.test-ghc-options.components.exes.test-ghc-options-exe}/test-stdout
26
26
27 echo '${concatStringsSep " " packageNames}' > $out
27 echo '${concatStringsSep " " packageNames}' > $out
28 '';
28 '';
test/project-flags/cabal.nix+1−1
@@ -21,7 +21,7 @@ in recurseIntoAttrs {
21 exe="${packages.test-project-flags.components.exes.test-project-flags-exe}/bin/test-project-flags-exe${stdenv.hostPlatform.extensions.executable}"
21 exe="${packages.test-project-flags.components.exes.test-project-flags-exe}/bin/test-project-flags-exe${stdenv.hostPlatform.extensions.executable}"
22
22
23 printf "checking whether executable runs... " >& 2
23 printf "checking whether executable runs... " >& 2
24− cat ${haskellLib.check packages.test-project-flags.components.exes.test-project-flags-exe}
24+ cat ${haskellLib.check packages.test-project-flags.components.exes.test-project-flags-exe}/test-stdout
25
25
26 touch $out
26 touch $out
27 '';
27 '';
test/project-flags/stack.nix+1−1
@@ -19,7 +19,7 @@ in recurseIntoAttrs {
19 exe="${packages.test-project-flags.components.exes.test-project-flags-exe}/bin/test-project-flags-exe${stdenv.hostPlatform.extensions.executable}"
19 exe="${packages.test-project-flags.components.exes.test-project-flags-exe}/bin/test-project-flags-exe${stdenv.hostPlatform.extensions.executable}"
20
20
21 printf "checking whether executable runs... " >& 2
21 printf "checking whether executable runs... " >& 2
22− cat ${haskellLib.check packages.test-project-flags.components.exes.test-project-flags-exe}
22+ cat ${haskellLib.check packages.test-project-flags.components.exes.test-project-flags-exe}/test-stdout
23
23
24 touch $out
24 touch $out
25 '';
25 '';
test/sublib-docs/default.nix+1−1
@@ -26,7 +26,7 @@ in recurseIntoAttrs {
26
26
27 # fixme: run on target platform when cross-compiled
27 # fixme: run on target platform when cross-compiled
28 printf "checking whether executable runs... " >& 2
28 printf "checking whether executable runs... " >& 2
29− cat ${haskellLib.check packages.sublib-docs.components.exes.sublib-docs}
29+ cat ${haskellLib.check packages.sublib-docs.components.exes.sublib-docs}/test-stdout
30
30
31 '' +
31 '' +
32 # Musl and Aarch are statically linked..
32 # Musl and Aarch are statically linked..