mirror of
https://github.com/ilyakooo0/haskell.nix.git
synced 2024-10-26 09:37:17 +03:00
Add support for coverage (#762)
- Added the ability to generate coverage reports for packages and projects. - Outputs mix and tix information, as well as a HTML report. - Added the "doCoverage" module option that allows users to choose packages to enable coverage for. - Added a "doCoverage" flag to the component builder that outputs HPC information when coverage is enabled. - Added the "overrideModules" library function to make it more ergonomic fo users to enable coverage on existing projects. - Modified the "check" builder to also output ".tix" files (if they exist). This information is required to generate the coverage report. - Added a test for coverage.
This commit is contained in:
parent
742932a968
commit
48b8674f5f
@ -53,6 +53,9 @@ let self =
|
||||
, enableExecutableProfiling ? component.enableExecutableProfiling
|
||||
, profilingDetail ? component.profilingDetail
|
||||
|
||||
# Coverage
|
||||
, doCoverage ? component.doCoverage
|
||||
|
||||
# Data
|
||||
, enableSeparateDataOutput ? component.enableSeparateDataOutput
|
||||
|
||||
@ -118,6 +121,7 @@ let
|
||||
(enableFeature enableExecutableProfiling "executable-profiling")
|
||||
(enableFeature enableStatic "static")
|
||||
(enableFeature enableShared "shared")
|
||||
(enableFeature doCoverage "coverage")
|
||||
] ++ lib.optionals (stdenv.hostPlatform.isMusl && (haskellLib.isExecutableType componentId)) [
|
||||
# These flags will make sure the resulting executable is statically linked.
|
||||
# If it uses other libraries it may be necessary for to add more
|
||||
@ -358,6 +362,11 @@ let
|
||||
fi
|
||||
done
|
||||
'')
|
||||
+ (lib.optionalString doCoverage ''
|
||||
mkdir -p $out/share
|
||||
cp -r dist/hpc $out/share
|
||||
cp dist/setup-config $out/
|
||||
'')
|
||||
}
|
||||
runHook postInstall
|
||||
'' + (lib.optionalString keepSource ''
|
||||
|
@ -1,6 +1,15 @@
|
||||
This file contains a summary of changes to Haskell.nix and `nix-tools`
|
||||
that will impact users.
|
||||
|
||||
## Sep 8, 2020
|
||||
* Added the ability to generate coverage reports for packages and
|
||||
projects.
|
||||
* Added the `doCoverage` module option that allows users to choose
|
||||
packages to enable coverage for.
|
||||
* Added a `doCoverage` flag to the component builder that outputs HPC
|
||||
information when coverage is enabled.
|
||||
* Added test for coverage.
|
||||
|
||||
## July 21, 2020
|
||||
* Removed `components.all`, use `symlinkJoin` on components.exes or
|
||||
`shellFor` if you need a shell.
|
||||
|
180
docs/dev/coverage.md
Normal file
180
docs/dev/coverage.md
Normal file
@ -0,0 +1,180 @@
|
||||
# Developer Coverage Overview
|
||||
|
||||
## Building
|
||||
|
||||
The implementation of coverage starts with the "doCoverage" flag on
|
||||
the builder in `comp-builder.nix`. The doCoverage flag enables and
|
||||
disables the Cabal coverage flag and copies any generated coverage
|
||||
data to "$out/share/hpc".
|
||||
|
||||
### Mix and tix files
|
||||
|
||||
The coverage information for any derivation consists of "mix" and
|
||||
"tix" files.
|
||||
|
||||
Mix files record static information about a source file and are
|
||||
generated at build time. They primarily contain a path to the source
|
||||
file and information about expressions and regions of the source file,
|
||||
which are later referenced by tix files.
|
||||
|
||||
Tix files contain dynamic information about a test run, recording when
|
||||
a portion of a source file is touched by a test. These are generated
|
||||
when the test is run.
|
||||
|
||||
### Multiple local packages
|
||||
|
||||
In the context of multiple local packages, there are a few types of
|
||||
coverage we might be interested in:
|
||||
- How well does the tests for this package cover the package library?
|
||||
- How well does the tests for this package cover the libraries of
|
||||
other packages in this project?
|
||||
- Both of the above.
|
||||
|
||||
To facilitate expressing any of these classifications of coverage, the
|
||||
`lib/cover.nix` function provides the `mixLibraries` argument. If
|
||||
you're just interested in how the tests cover the package library, you
|
||||
provide that library as an argument to `mixLibraries`. If you're
|
||||
interested in how the tests also cover other local packages in the
|
||||
project, you can also provide those libraries as arguments to
|
||||
mixLibraries.
|
||||
|
||||
The `projectCoverageReport` and `coverageReport` attributes that are
|
||||
provided by default on projects and packages respectively provide
|
||||
coverage information for *all* local packages in the project. This is
|
||||
to mimic the behaviour of Stack, which seems to be the expectation of
|
||||
most people. Of course, you can use the `projectCoverageReport` and
|
||||
`coverageReport` functions to construct your own custom coverage
|
||||
reports (as detailed in the [coverage tutorial](../tutorials/coverage.md#custom)).
|
||||
|
||||
## Coverage reports
|
||||
|
||||
### Package reports
|
||||
|
||||
The coverage information generated will look something like this:
|
||||
|
||||
```bash
|
||||
/nix/store/...-my-project-0.1.0.0-coverage-report/
|
||||
└── share
|
||||
└── hpc
|
||||
└── vanilla
|
||||
├── html
|
||||
│ └── my-library-0.1.0.0
|
||||
│ ├── my-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
|
||||
│ │ ├── My.Lib.Config.hs.html
|
||||
│ │ ├── My.Lib.Types.hs.html
|
||||
│ │ └── My.Lib.Util.hs.html
|
||||
│ ├── hpc_index_alt.html
|
||||
│ ├── hpc_index_exp.html
|
||||
│ ├── hpc_index_fun.html
|
||||
│ └── hpc_index.html
|
||||
├── mix
|
||||
│ └── my-library-0.1.0.0
|
||||
│ └── my-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
|
||||
│ ├── My.Lib.Config.mix
|
||||
│ ├── My.Lib.Types.mix
|
||||
│ └── My.Lib.Util.mix
|
||||
└── tix
|
||||
└── my-library-0.1.0.0
|
||||
├── my-library-0.1.0.0.tix
|
||||
├── my-test-1
|
||||
│ └── my-test-1.tix
|
||||
└── unit-test
|
||||
└── unit-test.tix
|
||||
```
|
||||
|
||||
- The mix files are copied verbatim from the library built with
|
||||
coverage.
|
||||
- The tix files for each test are copied from the check run verbatim
|
||||
and are output to ".../tix/<libraryname>/<testname>/<testname>.tix".
|
||||
- The tix files for each library are generated by summing the tix
|
||||
files for each test, but excluding any test modules. This tix file
|
||||
is output to ".../tix/<libraryname>/<libraryname>.tix".
|
||||
- Test modules are determined by inspecting the plan for the project
|
||||
(i.e. for the project "my-project" and test-suite "my-test-1", the
|
||||
test modules are read from:
|
||||
`my-project.checks.my-test-1.config.modules`)
|
||||
- The hpc HTML reports for each library are generated from their
|
||||
respective tix files (i.e. the
|
||||
`share/hpc/vanilla/html/my-library-0.1.0.0` report is generated from
|
||||
the
|
||||
`share/hpc/vanilla/tix/my-library-0.1.0.0/my-library-0.1.0.0.tix`
|
||||
file)
|
||||
|
||||
### Project-wide reports
|
||||
|
||||
The coverage information for an entire project will look something
|
||||
like this:
|
||||
|
||||
```bash
|
||||
/nix/store/...-coverage-report
|
||||
└── share
|
||||
└── hpc
|
||||
└── vanilla
|
||||
├── html
|
||||
│ ├── index.html
|
||||
│ ├── all
|
||||
│ │ ├── my-library-0.1.0.0-ERSaOroBZhe9awsoBkhmcV
|
||||
│ │ │ ├── My.Lib.Config.hs.html
|
||||
│ │ │ ├── My.Lib.Types.hs.html
|
||||
│ │ │ └── My.Lib.Util.hs.html
|
||||
│ │ ├── other-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
|
||||
│ │ │ ├── Other.Lib.A.hs.html
|
||||
│ │ │ └── Other.Lib.B.hs.html
|
||||
│ │ ├── hpc_index_alt.html
|
||||
│ │ ├── hpc_index_exp.html
|
||||
│ │ ├── hpc_index_fun.html
|
||||
│ │ └── hpc_index.html
|
||||
│ ├── my-library-0.1.0.0
|
||||
│ │ ├── my-library-0.1.0.0-ERSaOroBZhe9awsoBkhmcV
|
||||
│ │ │ ├── My.Lib.Config.hs.html
|
||||
│ │ │ ├── My.Lib.Types.hs.html
|
||||
│ │ │ └── My.Lib.Util.hs.html
|
||||
│ │ ├── hpc_index_alt.html
|
||||
│ │ ├── hpc_index_exp.html
|
||||
│ │ ├── hpc_index_fun.html
|
||||
│ │ └── hpc_index.html
|
||||
│ └── other-libray-0.1.0.0
|
||||
│ ├── other-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
|
||||
│ │ ├── Other.Lib.A.hs.html
|
||||
│ │ └── Other.Lib.B.hs.html
|
||||
│ ├── hpc_index_alt.html
|
||||
│ ├── hpc_index_exp.html
|
||||
│ ├── hpc_index_fun.html
|
||||
│ └── hpc_index.html
|
||||
├── mix
|
||||
│ ├── my-library-0.1.0.0-ERSaOroBZhe9awsoBkhmcV
|
||||
│ │ ├── My.Lib.Config.mix
|
||||
│ │ ├── My.Lib.Types.mix
|
||||
│ │ └── My.Lib.Util.mix
|
||||
│ └── other-library-0.1.0.0-48EVZBwW9Kj29VTaRMhBDf
|
||||
│ ├── Other.Lib.A.mix
|
||||
│ └── Other.Lib.B.mix
|
||||
└── tix
|
||||
├── all
|
||||
│ └── all.tix
|
||||
├── my-library-0.1.0.0
|
||||
│ ├── my-library-0.1.0.0.tix
|
||||
│ ├── my-test-1
|
||||
│ │ └── my-test-1.tix
|
||||
│ └── unit-test
|
||||
│ └── unit-test.tix
|
||||
└── another-library-0.1.0.0
|
||||
├── another-library-0.1.0.0.tix
|
||||
├── my-test-2
|
||||
│ └── my-test-2.tix
|
||||
└── unit-test
|
||||
└── unit-test.tix
|
||||
```
|
||||
|
||||
All of the coverage information is copied verbatim from the coverage
|
||||
reports for each of the constituent packages. A few additions are
|
||||
made:
|
||||
- `tix/all/all.tix` is generated from the union of all the library
|
||||
tix files.
|
||||
- We use this file when generating coverage reports for
|
||||
"coveralls.io".
|
||||
- An index page (`html/index.html`) is generated which links to the
|
||||
HTML coverage reports of the constituent packages.
|
||||
- A synthetic HTML report is generated from the `tix/all/all.tix`
|
||||
file. This shows the union of all the coverage information
|
||||
generated by each constituent coverage report.
|
123
docs/tutorials/coverage.md
Normal file
123
docs/tutorials/coverage.md
Normal file
@ -0,0 +1,123 @@
|
||||
# Coverage
|
||||
|
||||
haskell.nix can generate coverage information for your package or
|
||||
project using Cabal's inbuilt hpc support.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To get a sensible coverage report, you need to enable coverage on each
|
||||
of the packages of your project:
|
||||
|
||||
```nix
|
||||
pkgs.haskell-nix.project {
|
||||
src = pkgs.haskell-nix.haskellLib.cleanGit {
|
||||
name = "haskell-nix-project";
|
||||
src = ./.;
|
||||
};
|
||||
compiler-nix-name = "ghc884";
|
||||
|
||||
modules = [{
|
||||
packages.$pkg.components.library.doCoverage = true;
|
||||
}];
|
||||
}
|
||||
```
|
||||
|
||||
If you would like to make coverage optional, add an argument to your nix expression:
|
||||
|
||||
```nix
|
||||
{ withCoverage ? false }:
|
||||
|
||||
pkgs.haskell-nix.project {
|
||||
src = pkgs.haskell-nix.haskellLib.cleanGit {
|
||||
name = "haskell-nix-project";
|
||||
src = ./.;
|
||||
};
|
||||
compiler-nix-name = "ghc884";
|
||||
|
||||
modules = pkgs.lib.optional withCoverage [{
|
||||
packages.$pkg.components.library.doCoverage = true;
|
||||
}];
|
||||
}
|
||||
```
|
||||
|
||||
## Per-package
|
||||
|
||||
```bash
|
||||
nix-build default.nix -A "projectWithCoverage.$pkg.coverageReport"
|
||||
```
|
||||
|
||||
This will generate a coverage report for the package you requested.
|
||||
All tests that are enabled (configured with `doCheck == true`) are
|
||||
included in the coverage report.
|
||||
|
||||
See the [developer coverage docs](../dev/coverage.md#package-reports) for more information.
|
||||
|
||||
## Project-wide
|
||||
|
||||
```bash
|
||||
nix-build default.nix -A "projectWithCoverage.projectCoverageReport"
|
||||
```
|
||||
|
||||
This will generate a coverage report for all the local packages in
|
||||
your project.
|
||||
|
||||
See the [developer coverage docs](../dev/coverage.md#project-wide-reports) for more information.
|
||||
|
||||
## Custom
|
||||
|
||||
By default, the behaviour of the `coverageReport` attribute is to
|
||||
generate a coverage report that describes how that package affects the
|
||||
coverage of all local packages (including itself) in the project.
|
||||
|
||||
The default behaviour of `projectCoverageReport` is to sum the
|
||||
default coverage reports (produced by the above process) of all local
|
||||
packages in the project.
|
||||
|
||||
You can modify this behaviour by using the `coverageReport` and
|
||||
`projectCoverageReport` functions found in the haskell.nix library:
|
||||
|
||||
```nix
|
||||
let
|
||||
inherit (pkgs.haskell-nix) haskellLib;
|
||||
|
||||
project = haskellLib.project {
|
||||
src = pkgs.haskell-nix.haskellLib.cleanGit {
|
||||
name = "haskell-nix-project";
|
||||
src = ./.;
|
||||
};
|
||||
compiler-nix-name = "ghc884";
|
||||
|
||||
modules = [{
|
||||
packages.$pkgA.components.library.doCoverage = true;
|
||||
packages.$pkgB.components.library.doCoverage = true;
|
||||
}];
|
||||
};
|
||||
|
||||
# Generate a coverage report for $pkgA that only includes the
|
||||
# unit-test check and only shows coverage information for $pkgA, not
|
||||
# $pkgB.
|
||||
custom$pkgACoverageReport = haskellLib.coverageReport rec {
|
||||
name = "$pkgA-unit-tests-only"
|
||||
inherit (project.$pkgA.components) library;
|
||||
checks = [project.$pkgA.components.checks.unit-test];
|
||||
# Note that this is the default value of the "mixLibraries"
|
||||
# argument and so this line isn't really necessary.
|
||||
mixLibraries = [project.$pkgA.components.library];
|
||||
};
|
||||
|
||||
custom$pkgBCoverageReport = haskellLib.coverageReport rec {
|
||||
name = "$pkgB-unit-tests-only"
|
||||
inherit (project.$pkgB.components) library;
|
||||
checks = [project.$pkgB.components.checks.unit-test];
|
||||
mixLibraries = [project.$pkgB.components.library];
|
||||
};
|
||||
|
||||
# Generate a project coverage report that only includes the unit
|
||||
# tests of the project, and only shows how each unit test effects
|
||||
# the coverage of it's package, and not other packages in the
|
||||
# project.
|
||||
allUnitTestsProjectReport = haskellLib.projectCoverageReport [custom$pkgACoverageReport custom$pkgBCoverageReport];
|
||||
in {
|
||||
inherit project custom$pkgACoverageReport custom$pkgBCoverageReport allUnitTestsProjectCoverageReport;
|
||||
}
|
||||
```
|
@ -15,7 +15,7 @@ in stdenv.mkDerivation ({
|
||||
src = drv.source or (srcOnly drv);
|
||||
|
||||
passthru = {
|
||||
inherit (drv) identifier config configFiles executableToolDepends cleanSrc env;
|
||||
inherit (drv) identifier config configFiles executableToolDepends cleanSrc env exeName;
|
||||
};
|
||||
|
||||
inherit (drv) meta LANG LC_ALL buildInputs nativeBuildInputs;
|
||||
@ -27,11 +27,14 @@ in stdenv.mkDerivation ({
|
||||
# If doCheck or doCrossCheck are false we may still build this
|
||||
# component and we want it to quietly succeed.
|
||||
buildPhase = ''
|
||||
touch $out
|
||||
mkdir $out
|
||||
|
||||
runHook preCheck
|
||||
|
||||
${toString component.testWrapper} ${drv}/bin/${drv.exeName} ${lib.concatStringsSep " " component.testFlags} | tee $out
|
||||
${toString component.testWrapper} ${drv}/bin/${drv.exeName} ${lib.concatStringsSep " " component.testFlags} | tee $out/test-stdout
|
||||
|
||||
# Copy over tix files, if they exist
|
||||
find . -iname '${drv.exeName}.tix' -exec mkdir -p $out/share/hpc/vanilla/tix/${drv.exeName} \; -exec cp {} $out/share/hpc/vanilla/tix/${drv.exeName}/ \;
|
||||
|
||||
runHook postCheck
|
||||
'';
|
||||
|
150
lib/cover-project.nix
Normal file
150
lib/cover-project.nix
Normal file
@ -0,0 +1,150 @@
|
||||
# A project coverage report is a composition of package coverage
|
||||
# reports
|
||||
{ stdenv, pkgs, lib, haskellLib }:
|
||||
|
||||
# List of coverage reports to accumulate
|
||||
coverageReports:
|
||||
|
||||
let
|
||||
toBashArray = arr: "(" + (lib.concatStringsSep " " arr) + ")";
|
||||
|
||||
# Create table rows for a project coverage index page that look something like:
|
||||
#
|
||||
# | Package |
|
||||
# |------------------|
|
||||
# | cardano-shell |
|
||||
# | cardano-launcher |
|
||||
coverageTableRows = coverageReport:
|
||||
''
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${coverageReport.passthru.name}/hpc_index.html">${coverageReport.passthru.name}</href>
|
||||
</td>
|
||||
</tr>
|
||||
'';
|
||||
|
||||
projectIndexHtml = pkgs.writeText "index.html" ''
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<table border="1" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Report</th>
|
||||
</tr>
|
||||
|
||||
${with lib; concatStringsSep "\n" (map coverageTableRows coverageReports)}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
'';
|
||||
|
||||
ghc =
|
||||
if (builtins.length coverageReports) > 0
|
||||
then (builtins.head coverageReports).library.project.pkg-set.config.ghc.package or pkgs.ghc
|
||||
else pkgs.ghc;
|
||||
|
||||
libs = map (r: r.library) coverageReports;
|
||||
|
||||
projectLibs = map (pkg: pkg.components.library) (lib.attrValues (haskellLib.selectProjectPackages ((lib.head libs).project.hsPkgs)));
|
||||
|
||||
mixDirs =
|
||||
map
|
||||
(l: "${l}/share/hpc/vanilla/mix/${l.identifier.name}-${l.identifier.version}")
|
||||
(projectLibs);
|
||||
|
||||
srcDirs = map (l: l.src.outPath) (projectLibs);
|
||||
|
||||
in pkgs.runCommand "project-coverage-report"
|
||||
({ buildInputs = [ghc];
|
||||
LANG = "en_US.UTF-8";
|
||||
LC_ALL = "en_US.UTF-8";
|
||||
} // lib.optionalAttrs (stdenv.buildPlatform.libc == "glibc") {
|
||||
LOCALE_ARCHIVE = "${pkgs.buildPackages.glibcLocales}/lib/locale/locale-archive";
|
||||
})
|
||||
''
|
||||
function markup() {
|
||||
local -n srcDs=$1
|
||||
local -n mixDs=$2
|
||||
local -n includedModules=$3
|
||||
local destDir=$4
|
||||
local tixFile=$5
|
||||
|
||||
local hpcMarkupCmd=("hpc" "markup" "--destdir=$destDir")
|
||||
for srcDir in "''${srcDs[@]}"; do
|
||||
hpcMarkupCmd+=("--srcdir=$srcDir")
|
||||
done
|
||||
|
||||
for mixDir in "''${mixDs[@]}"; do
|
||||
hpcMarkupCmd+=("--hpcdir=$mixDir")
|
||||
done
|
||||
|
||||
for module in "''${includedModules[@]}"; do
|
||||
hpcMarkupCmd+=("--include=$module")
|
||||
done
|
||||
|
||||
hpcMarkupCmd+=("$tixFile")
|
||||
|
||||
echo "''${hpcMarkupCmd[@]}"
|
||||
eval "''${hpcMarkupCmd[@]}"
|
||||
}
|
||||
|
||||
function findModules() {
|
||||
local searchDir=$2
|
||||
local pattern=$3
|
||||
|
||||
pushd $searchDir
|
||||
mapfile -d $'\0' $1 < <(find ./ -type f \
|
||||
-wholename "$pattern" -not -name "Paths*" \
|
||||
-exec basename {} \; \
|
||||
| sed "s/\.mix$//" \
|
||||
| tr "\n" "\0")
|
||||
popd
|
||||
}
|
||||
|
||||
mkdir -p $out/share/hpc/vanilla/tix/all
|
||||
mkdir -p $out/share/hpc/vanilla/mix/
|
||||
mkdir -p $out/share/hpc/vanilla/html/
|
||||
|
||||
# Find all tix files in each package
|
||||
tixFiles=()
|
||||
${with lib; concatStringsSep "\n" (map (coverageReport: ''
|
||||
identifier="${coverageReport.name}"
|
||||
report=${coverageReport}
|
||||
tix="$report/share/hpc/vanilla/tix/$identifier/$identifier.tix"
|
||||
if test -f "$tix"; then
|
||||
tixFiles+=("$tix")
|
||||
fi
|
||||
|
||||
# Copy mix, tix, and html information over from each report
|
||||
cp -Rn $report/share/hpc/vanilla/mix/$identifier/* $out/share/hpc/vanilla/mix/
|
||||
cp -R $report/share/hpc/vanilla/tix/* $out/share/hpc/vanilla/tix/
|
||||
cp -R $report/share/hpc/vanilla/html/* $out/share/hpc/vanilla/html/
|
||||
'') coverageReports)}
|
||||
|
||||
if [ ''${#tixFiles[@]} -ne 0 ]; then
|
||||
# Create tix file with test run information for all packages
|
||||
tixFile="$out/share/hpc/vanilla/tix/all/all.tix"
|
||||
hpcSumCmd=("hpc" "sum" "--union" "--output=$tixFile")
|
||||
hpcSumCmd+=("''${tixFiles[@]}")
|
||||
echo "''${hpcSumCmd[@]}"
|
||||
eval "''${hpcSumCmd[@]}"
|
||||
|
||||
# Markup a HTML coverage report for the entire project
|
||||
cp ${projectIndexHtml} $out/share/hpc/vanilla/html/index.html
|
||||
|
||||
local markupOutDir="$out/share/hpc/vanilla/html/all"
|
||||
local srcDirs=${toBashArray srcDirs}
|
||||
local mixDirs=${toBashArray mixDirs}
|
||||
local allMixModules=()
|
||||
|
||||
mkdir $markupOutDir
|
||||
findModules allMixModules "$out/share/hpc/vanilla/mix/" "*.mix"
|
||||
|
||||
markup srcDirs mixDirs allMixModules "$markupOutDir" "$tixFile"
|
||||
fi
|
||||
''
|
165
lib/cover.nix
Normal file
165
lib/cover.nix
Normal file
@ -0,0 +1,165 @@
|
||||
{ stdenv, lib, haskellLib, pkgs }:
|
||||
|
||||
# Name of the coverage report, which should be unique
|
||||
{ name
|
||||
# Library to check coverage of
|
||||
, library
|
||||
# List of check derivations that generate coverage
|
||||
, checks
|
||||
# List of other libraries to include in the coverage report. The
|
||||
# default value if just the derivation provided as the `library`
|
||||
# argument. Use a larger list of libraries if you would like the tests
|
||||
# of one local package to generate coverage for another.
|
||||
, mixLibraries ? [library]
|
||||
# hack for project-less projects
|
||||
, ghc ? library.project.pkg-set.config.ghc.package
|
||||
}:
|
||||
|
||||
let
|
||||
toBashArray = arr: "(" + (lib.concatStringsSep " " arr) + ")";
|
||||
|
||||
mixDir = l: "${l}/share/hpc/vanilla/mix/${l.identifier.name}-${l.identifier.version}";
|
||||
mixDirs = map mixDir mixLibraries;
|
||||
|
||||
srcDirs = map (l: l.src.outPath) mixLibraries;
|
||||
|
||||
in pkgs.runCommand (name + "-coverage-report")
|
||||
({ buildInputs = [ ghc ];
|
||||
passthru = {
|
||||
inherit name library checks;
|
||||
};
|
||||
# HPC will fail if the Haskell file contains non-ASCII characters,
|
||||
# unless our locale is set correctly. This has been fixed, but we
|
||||
# don't know what version of HPC we will be using, hence we should
|
||||
# always use the workaround.
|
||||
# https://gitlab.haskell.org/ghc/ghc/-/issues/17073
|
||||
LANG = "en_US.UTF-8";
|
||||
LC_ALL = "en_US.UTF-8";
|
||||
} // lib.optionalAttrs (stdenv.buildPlatform.libc == "glibc") {
|
||||
LOCALE_ARCHIVE = "${pkgs.buildPackages.glibcLocales}/lib/locale/locale-archive";
|
||||
})
|
||||
''
|
||||
function markup() {
|
||||
local -n srcDs=$1
|
||||
local -n mixDs=$2
|
||||
local -n includedModules=$3
|
||||
local destDir=$4
|
||||
local tixFile=$5
|
||||
|
||||
local hpcMarkupCmd=("hpc" "markup" "--destdir=$destDir")
|
||||
for srcDir in "''${srcDs[@]}"; do
|
||||
hpcMarkupCmd+=("--srcdir=$srcDir")
|
||||
done
|
||||
|
||||
for mixDir in "''${mixDs[@]}"; do
|
||||
hpcMarkupCmd+=("--hpcdir=$mixDir")
|
||||
done
|
||||
|
||||
for module in "''${includedModules[@]}"; do
|
||||
hpcMarkupCmd+=("--include=$module")
|
||||
done
|
||||
|
||||
hpcMarkupCmd+=("$tixFile")
|
||||
|
||||
echo "''${hpcMarkupCmd[@]}"
|
||||
eval "''${hpcMarkupCmd[@]}"
|
||||
}
|
||||
|
||||
function sumTix() {
|
||||
local -n includedModules=$1
|
||||
local -n tixFs=$2
|
||||
local outFile="$3"
|
||||
|
||||
local hpcSumCmd=("hpc" "sum" "--union" "--output=$outFile")
|
||||
|
||||
for module in "''${includedModules[@]}"; do
|
||||
hpcSumCmd+=("--include=$module")
|
||||
done
|
||||
|
||||
for tixFile in "''${tixFs[@]}"; do
|
||||
hpcSumCmd+=("$tixFile")
|
||||
done
|
||||
|
||||
echo "''${hpcSumCmd[@]}"
|
||||
eval "''${hpcSumCmd[@]}"
|
||||
}
|
||||
|
||||
function findModules() {
|
||||
local searchDir=$2
|
||||
local pattern=$3
|
||||
|
||||
pushd $searchDir
|
||||
mapfile -d $'\0' $1 < <(find ./ -type f \
|
||||
-wholename "$pattern" -not -name "Paths*" \
|
||||
-exec basename {} \; \
|
||||
| sed "s/\.mix$//" \
|
||||
| tr "\n" "\0")
|
||||
popd
|
||||
}
|
||||
|
||||
local mixDirs=${toBashArray mixDirs}
|
||||
|
||||
mkdir -p $out/share/hpc/vanilla/mix/${name}
|
||||
mkdir -p $out/share/hpc/vanilla/tix/${name}
|
||||
mkdir -p $out/share/hpc/vanilla/html/${name}
|
||||
|
||||
# Copy over mix files verbatim
|
||||
for dir in "''${mixDirs[@]}"; do
|
||||
if [ -d "$dir" ]; then
|
||||
cp -R "$dir"/* $out/share/hpc/vanilla/mix/${name}
|
||||
fi
|
||||
done
|
||||
|
||||
local srcDirs=${toBashArray srcDirs}
|
||||
local allMixModules=()
|
||||
local pkgMixModules=()
|
||||
|
||||
# The behaviour of stack coverage reports is to provide tix files
|
||||
# that include coverage information for every local package, but
|
||||
# to provide HTML reports that only include coverage info for the
|
||||
# current package. We emulate the same behaviour here. If the user
|
||||
# includes all local packages in the mix libraries argument, they
|
||||
# will get a coverage report very similar to stack.
|
||||
|
||||
# All mix modules
|
||||
findModules allMixModules "$out/share/hpc/vanilla/mix/${name}" "*.mix"
|
||||
# Only mix modules corresponding to this package
|
||||
findModules pkgMixModules "$out/share/hpc/vanilla/mix/${name}" "*${name}*/*.mix"
|
||||
|
||||
# For each test
|
||||
local tixFiles=()
|
||||
${lib.concatStringsSep "\n" (builtins.map (check: ''
|
||||
if [ -d "${check}/share/hpc/vanilla/tix" ]; then
|
||||
pushd ${check}/share/hpc/vanilla/tix
|
||||
|
||||
tixFile="$(find . -iwholename "*.tix" -type f -print -quit)"
|
||||
local newTixFile=$out/share/hpc/vanilla/tix/${name}/"$tixFile"
|
||||
|
||||
mkdir -p "$(dirname $newTixFile)"
|
||||
# Copy over the tix file verbatim
|
||||
cp "$tixFile" "$newTixFile"
|
||||
|
||||
# Add the tix file to our list
|
||||
tixFiles+=("$newTixFile")
|
||||
|
||||
# Create a coverage report for *just that test*
|
||||
markup srcDirs mixDirs pkgMixModules "$out/share/hpc/vanilla/html/${name}/${check.exeName}/" "$newTixFile"
|
||||
|
||||
popd
|
||||
fi
|
||||
'') checks)
|
||||
}
|
||||
|
||||
# Sum tix files to create a tix file with all relevant tix
|
||||
# information and markup a HTML report from this info.
|
||||
if (( "''${#tixFiles[@]}" > 0 )); then
|
||||
local sumTixFile="$out/share/hpc/vanilla/tix/${name}/${name}.tix"
|
||||
local markupOutDir="$out/share/hpc/vanilla/html/${name}"
|
||||
|
||||
# Sum all of our tix file, including modules from any local package
|
||||
sumTix allMixModules tixFiles "$sumTixFile"
|
||||
|
||||
# Markup a HTML report, included modules from only this package
|
||||
markup srcDirs mixDirs pkgMixModules "$markupOutDir" "$sumTixFile"
|
||||
fi
|
||||
''
|
@ -232,6 +232,16 @@ in {
|
||||
inherit stdenv lib haskellLib srcOnly;
|
||||
};
|
||||
|
||||
# Do coverage of a package
|
||||
coverageReport = import ./cover.nix {
|
||||
inherit stdenv lib haskellLib pkgs;
|
||||
};
|
||||
|
||||
# Do coverage of a project
|
||||
projectCoverageReport = import ./cover-project.nix {
|
||||
inherit stdenv lib haskellLib pkgs;
|
||||
};
|
||||
|
||||
# Use `isCrossHost` to identify when we are cross compiling and
|
||||
# the code we are producing will not run on the build system
|
||||
# without an emulator.
|
||||
|
@ -35,6 +35,7 @@ pages:
|
||||
- 'Bumping Hackage and Stackage snapshots': tutorials/hackage-stackage.md
|
||||
- 'Materialization: Speeding up Nix evaluation': tutorials/materialization.md
|
||||
- 'Cross-compiling your project': tutorials/cross-compilation.md
|
||||
- 'Generating coverage information': tutorials/coverage.md
|
||||
- 'Reference':
|
||||
- 'Suported GHC versions': reference/supported-ghc-versions.md
|
||||
- 'Command-line tools': reference/commands.md
|
||||
@ -52,4 +53,5 @@ pages:
|
||||
- 'Removing withPackage wrapper': dev/removing-with-package-wrapper.md
|
||||
- 'Test Suite': dev/tests.md
|
||||
- 'Adding a new GHC version': dev/adding-new-ghc.md
|
||||
- 'Coverage': dev/coverage.md
|
||||
- 'ChangeLog': changelog.md
|
||||
|
@ -108,6 +108,11 @@ let
|
||||
type = bool;
|
||||
default = (def.doQuickjump or true);
|
||||
};
|
||||
doCoverage = mkOption {
|
||||
description = "Enable production of test coverage reports.";
|
||||
type = bool;
|
||||
default = (def.doCoverage or false);
|
||||
};
|
||||
dontPatchELF = mkOption {
|
||||
description = "If set, the patchelf command is not used to remove unnecessary RPATH entries. Only applies to Linux.";
|
||||
type = bool;
|
||||
|
@ -482,7 +482,8 @@ final: prev: {
|
||||
{ compiler.nix-name = args.compiler-nix-name; };
|
||||
extra-hackages = args.extra-hackages or [];
|
||||
};
|
||||
in addProjectAndPackageAttrs {
|
||||
|
||||
project = addProjectAndPackageAttrs rec {
|
||||
inherit (pkg-set.config) hsPkgs;
|
||||
inherit pkg-set;
|
||||
plan-nix = callProjectResults.projectNix;
|
||||
@ -491,6 +492,8 @@ final: prev: {
|
||||
tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
|
||||
roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
|
||||
};
|
||||
in project;
|
||||
|
||||
|
||||
# Take `hsPkgs` from the `rawProject` and update all the packages and
|
||||
# components so they have a `.project` attribute and as well as
|
||||
@ -498,25 +501,34 @@ final: prev: {
|
||||
addProjectAndPackageAttrs = rawProject:
|
||||
final.lib.fix (project':
|
||||
let project = project' // { recurseForDerivations = false; };
|
||||
in rawProject // {
|
||||
in rawProject // rec {
|
||||
hsPkgs = (final.lib.mapAttrs (n: package':
|
||||
if package' == null
|
||||
then null
|
||||
else
|
||||
let package = package' // { recurseForDerivations = false; };
|
||||
in package' // {
|
||||
in package' // rec {
|
||||
components = final.lib.mapAttrs (n: v:
|
||||
if n == "library" || n == "all"
|
||||
then v // { inherit project package; }
|
||||
else final.lib.mapAttrs (_: c: c // { inherit project package; }) v
|
||||
) package'.components;
|
||||
inherit project;
|
||||
|
||||
coverageReport = haskellLib.coverageReport (rec {
|
||||
name = package.identifier.name + "-" + package.identifier.version;
|
||||
inherit (components) library;
|
||||
checks = final.lib.filter (final.lib.isDerivation) (final.lib.attrValues package'.checks);
|
||||
mixLibraries = map (pkg: pkg.components.library) (final.lib.attrValues (haskellLib.selectProjectPackages project.hsPkgs));
|
||||
});
|
||||
}
|
||||
) rawProject.hsPkgs
|
||||
// {
|
||||
# These are functions not packages
|
||||
inherit (rawProject.hsPkgs) shellFor ghcWithHoogle ghcWithPackages;
|
||||
});
|
||||
|
||||
projectCoverageReport = haskellLib.projectCoverageReport (map (pkg: pkg.coverageReport) (final.lib.attrValues (haskellLib.selectProjectPackages hsPkgs)));
|
||||
});
|
||||
|
||||
cabalProject =
|
||||
@ -525,7 +537,7 @@ final: prev: {
|
||||
args = { caller = "hackage-package"; } // args';
|
||||
p = cabalProject' args;
|
||||
in p.hsPkgs // {
|
||||
inherit (p) plan-nix index-state tool tools roots;
|
||||
inherit (p) plan-nix index-state tool tools roots projectCoverageReport;
|
||||
# Provide `nix-shell -A shells.ghc` for users migrating from the reflex-platform.
|
||||
# But we should encourage use of `nix-shell -A shellFor`
|
||||
shells.ghc = p.hsPkgs.shellFor {};
|
||||
@ -543,7 +555,8 @@ final: prev: {
|
||||
++ (args.modules or [])
|
||||
++ final.lib.optional (args ? ghc) { ghc.package = args.ghc; };
|
||||
};
|
||||
in addProjectAndPackageAttrs {
|
||||
|
||||
project = addProjectAndPackageAttrs {
|
||||
inherit (pkg-set.config) hsPkgs;
|
||||
inherit pkg-set;
|
||||
stack-nix = callProjectResults.projectNix;
|
||||
@ -551,10 +564,11 @@ final: prev: {
|
||||
tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
|
||||
roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
|
||||
};
|
||||
in project;
|
||||
|
||||
stackProject = args: let p = stackProject' args;
|
||||
in p.hsPkgs // {
|
||||
inherit (p) stack-nix tool tools roots;
|
||||
inherit (p) stack-nix tool tools roots projectCoverageReport;
|
||||
# Provide `nix-shell -A shells.ghc` for users migrating from the reflex-platform.
|
||||
# But we should encourage use of `nix-shell -A shellFor`
|
||||
shells.ghc = p.hsPkgs.shellFor {};
|
||||
|
@ -21,7 +21,7 @@ in recurseIntoAttrs {
|
||||
buildCommand =
|
||||
(concatStrings (mapAttrsToList (name: value: ''
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check value}
|
||||
cat ${haskellLib.check value}/test-stdout
|
||||
'') packages.buildable-test.components.exes)) + ''
|
||||
touch $out
|
||||
'';
|
||||
|
@ -26,7 +26,7 @@ in recurseIntoAttrs {
|
||||
|
||||
# fixme: run on target platform when cross-compiled
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.project.components.exes.project}
|
||||
cat ${haskellLib.check packages.project.components.exes.project}/test-stdout
|
||||
|
||||
'' +
|
||||
# Aarch is statically linked and does not produce a .so file.
|
||||
@ -56,10 +56,10 @@ in recurseIntoAttrs {
|
||||
touch $out
|
||||
|
||||
printf "checking whether benchmark ran... " >& 2
|
||||
cat ${haskellLib.check packages.project.components.benchmarks.project-bench}
|
||||
cat ${haskellLib.check packages.project.components.benchmarks.project-bench}/test-stdout
|
||||
|
||||
printf "checking whether tests ran... " >& 2
|
||||
cat ${haskellLib.check packages.project.components.tests.unit}
|
||||
cat ${haskellLib.check packages.project.components.tests.unit}/test-stdout
|
||||
'';
|
||||
|
||||
meta.platforms = platforms.all;
|
||||
|
@ -36,7 +36,7 @@ in recurseIntoAttrs {
|
||||
|
||||
# fixme: run on target platform when cross-compiled
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.cabal-hpack.components.exes.cabal-hpack}
|
||||
cat ${haskellLib.check packages.cabal-hpack.components.exes.cabal-hpack}/test-stdout
|
||||
'' + (if stdenv.hostPlatform.isMusl
|
||||
then ''
|
||||
printf "checking that executable is statically linked... " >& 2
|
||||
|
@ -39,7 +39,7 @@ in recurseIntoAttrs {
|
||||
|
||||
# fixme: run on target platform when cross-compiled
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.cabal-simple.components.exes.cabal-simple}
|
||||
cat ${haskellLib.check packages.cabal-simple.components.exes.cabal-simple}/test-stdout
|
||||
'' + (if stdenv.hostPlatform.isMusl
|
||||
then ''
|
||||
printf "checking that executable is statically linked... " >& 2
|
||||
|
@ -20,7 +20,7 @@ in recurseIntoAttrs {
|
||||
exe="${packages.use-cabal-simple.components.exes.use-cabal-simple}/bin/use-cabal-simple${stdenv.hostPlatform.extensions.executable}"
|
||||
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.use-cabal-simple.components.exes.use-cabal-simple}
|
||||
cat ${haskellLib.check packages.use-cabal-simple.components.exes.use-cabal-simple}/test-stdout
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
@ -20,7 +20,7 @@ in recurseIntoAttrs {
|
||||
exe="${packages.use-cabal-simple.components.exes.use-cabal-simple}/bin/use-cabal-simple${stdenv.hostPlatform.extensions.executable}"
|
||||
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.use-cabal-simple.components.exes.use-cabal-simple}
|
||||
cat ${haskellLib.check packages.use-cabal-simple.components.exes.use-cabal-simple}/test-stdout
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
@ -36,7 +36,7 @@ in recurseIntoAttrs {
|
||||
|
||||
# fixme: run on target platform when cross-compiled
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.cabal-sublib.components.exes.cabal-sublib}
|
||||
cat ${haskellLib.check packages.cabal-sublib.components.exes.cabal-sublib}/test-stdout
|
||||
|
||||
'' +
|
||||
# Musl and Aarch are statically linked..
|
||||
|
@ -29,7 +29,7 @@ in recurseIntoAttrs {
|
||||
exe="${packages.cabal-simple.components.exes.cabal-simple}/bin/cabal-simple${stdenv.hostPlatform.extensions.executable}"
|
||||
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.cabal-simple.components.exes.cabal-simple}
|
||||
cat ${haskellLib.check packages.cabal-simple.components.exes.cabal-simple}/test-stdout
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
@ -24,7 +24,7 @@ in recurseIntoAttrs {
|
||||
exe="${packages.stack-simple.components.exes.stack-simple-exe}/bin/stack-simple-exe${stdenv.hostPlatform.extensions.executable}"
|
||||
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.stack-simple.components.exes.stack-simple-exe}
|
||||
cat ${haskellLib.check packages.stack-simple.components.exes.stack-simple-exe}/test-stdout
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
2
test/coverage/cabal.project
Normal file
2
test/coverage/cabal.project
Normal file
@ -0,0 +1,2 @@
|
||||
packages: pkga
|
||||
pkgb
|
19
test/coverage/conduit.hs
Normal file
19
test/coverage/conduit.hs
Normal file
@ -0,0 +1,19 @@
|
||||
-- https://github.com/snoyberg/conduit#readme
|
||||
|
||||
import Conduit
|
||||
import System.Directory (removeFile)
|
||||
|
||||
main = do
|
||||
-- Pure operations: summing numbers.
|
||||
print $ runConduitPure $ yieldMany [1..10] .| sumC
|
||||
|
||||
-- Exception safe file access: copy a file.
|
||||
writeFile "input.txt" "This is a test." -- create the source file
|
||||
runConduitRes $ sourceFileBS "input.txt" .| sinkFile "output.txt" -- actual copying
|
||||
readFile "output.txt" >>= putStrLn -- prove that it worked
|
||||
|
||||
-- Perform transformations.
|
||||
print $ runConduitPure $ yieldMany [1..10] .| mapC (+ 1) .| sinkList
|
||||
|
||||
removeFile "input.txt"
|
||||
removeFile "output.txt"
|
127
test/coverage/default.nix
Normal file
127
test/coverage/default.nix
Normal file
@ -0,0 +1,127 @@
|
||||
{ stdenv, cabal-install, cabalProject', stackProject', recurseIntoAttrs, runCommand, testSrc, compiler-nix-name }:
|
||||
|
||||
with stdenv.lib;
|
||||
|
||||
let
|
||||
projectArgs = {
|
||||
src = testSrc "coverage";
|
||||
inherit compiler-nix-name;
|
||||
modules = [{
|
||||
# Package has no exposed modules which causes
|
||||
# haddock: No input file(s)
|
||||
packages.bytestring-builder.doHaddock = false;
|
||||
|
||||
# Coverage
|
||||
packages.pkga.components.library.doCoverage = true;
|
||||
packages.pkgb.components.library.doCoverage = true;
|
||||
}];
|
||||
};
|
||||
|
||||
cabalProj = (cabalProject' projectArgs);
|
||||
stackProj = (stackProject' projectArgs);
|
||||
|
||||
in recurseIntoAttrs ({
|
||||
run = stdenv.mkDerivation {
|
||||
name = "coverage-test";
|
||||
|
||||
buildCommand = ''
|
||||
########################################################################
|
||||
# test coverage reports with an example project
|
||||
|
||||
fileExistsNonEmpty() {
|
||||
local file=$1
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "Missing: $file"
|
||||
exit 1
|
||||
fi
|
||||
local filesize=$(command stat --format '%s' "$file")
|
||||
if [ $filesize -eq 0 ]; then
|
||||
echo "File must not be empty: $file"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
findFileExistsNonEmpty() {
|
||||
local searchDir=$1
|
||||
local filePattern=$2
|
||||
|
||||
local file="$(find $searchDir -name $filePattern -print -quit)"
|
||||
|
||||
if [ -z $file ]; then
|
||||
echo "Couldn't find file \"$filePattern\" in directory \"$searchDir\"."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local filesize=$(command stat --format '%s' "$file")
|
||||
if [ $filesize -eq 0 ]; then
|
||||
echo "File must not be empty: $file"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
dirExistsEmpty() {
|
||||
local dir=$1
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "Missing: $dir"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(ls -A $dir)" ]; then
|
||||
echo "Dir should be empty: $dir"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
dirExists() {
|
||||
local dir=$1
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "Missing: $dir"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
${concatStringsSep "\n" (map (project: ''
|
||||
pkga_basedir="${project.hsPkgs.pkga.coverageReport}/share/hpc/vanilla"
|
||||
findFileExistsNonEmpty "$pkga_basedir/mix/pkga-0.1.0.0/" "PkgA.mix"
|
||||
dirExistsEmpty "$pkga_basedir/tix/pkga-0.1.0.0"
|
||||
dirExistsEmpty "$pkga_basedir/html/pkga-0.1.0.0"
|
||||
|
||||
pkgb_basedir="${project.hsPkgs.pkgb.coverageReport}/share/hpc/vanilla"
|
||||
testTix="$pkgb_basedir/tix/pkgb-0.1.0.0/tests/tests.tix"
|
||||
libTix="$pkgb_basedir/tix/pkgb-0.1.0.0/pkgb-0.1.0.0.tix"
|
||||
fileExistsNonEmpty "$testTix"
|
||||
fileExistsNonEmpty "$libTix"
|
||||
findFileExistsNonEmpty "$pkgb_basedir/mix/pkgb-0.1.0.0/" "ConduitExample.mix"
|
||||
findFileExistsNonEmpty "$pkgb_basedir/mix/pkgb-0.1.0.0/" "PkgB.mix"
|
||||
fileExistsNonEmpty "$pkgb_basedir/html/pkgb-0.1.0.0/hpc_index.html"
|
||||
|
||||
filesizeTestsTix=$(command stat --format '%s' "$testTix")
|
||||
filesizeLibTix=$(command stat --format '%s' "$libTix")
|
||||
if (( filesizeTestsTix <= filesizeLibTix )); then
|
||||
echo "Filesize of \"$testTix\" ($filesizeTestsTix) should be greather than that of \"$libTix\" ($filesizeLibTix). Did you forget to exclude test modules when creating \"$libTix\"?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
project_basedir="${project.projectCoverageReport}/share/hpc/vanilla"
|
||||
fileExistsNonEmpty "$project_basedir/html/index.html"
|
||||
dirExists "$project_basedir/html/pkga-0.1.0.0"
|
||||
dirExists "$project_basedir/html/pkgb-0.1.0.0"
|
||||
findFileExistsNonEmpty "$project_basedir/mix/" "PkgA.mix"
|
||||
findFileExistsNonEmpty "$project_basedir/mix/" "PkgB.mix"
|
||||
findFileExistsNonEmpty "$project_basedir/mix/" "ConduitExample.mix"
|
||||
dirExists "$project_basedir/tix/all"
|
||||
fileExistsNonEmpty "$project_basedir/tix/all/all.tix"
|
||||
dirExists "$project_basedir/tix/pkga-0.1.0.0"
|
||||
dirExists "$project_basedir/tix/pkgb-0.1.0.0"
|
||||
fileExistsNonEmpty "$project_basedir/tix/pkgb-0.1.0.0/pkgb-0.1.0.0.tix"
|
||||
dirExists "$project_basedir/tix/pkgb-0.1.0.0/tests"
|
||||
fileExistsNonEmpty "$project_basedir/tix/pkgb-0.1.0.0/tests/tests.tix"
|
||||
'') [ cabalProj stackProj ])}
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
||||
meta.platforms = platforms.all;
|
||||
|
||||
passthru = {
|
||||
# Used for debugging with nix repl
|
||||
inherit cabalProj stackProj;
|
||||
};
|
||||
};
|
||||
})
|
4
test/coverage/pkga/MainA.hs
Normal file
4
test/coverage/pkga/MainA.hs
Normal file
@ -0,0 +1,4 @@
|
||||
module Main where
|
||||
|
||||
main :: IO ()
|
||||
main = putStrLn "This is MainA"
|
16
test/coverage/pkga/PkgA.hs
Normal file
16
test/coverage/pkga/PkgA.hs
Normal file
@ -0,0 +1,16 @@
|
||||
module PkgA (decode) where
|
||||
|
||||
import Control.Lens
|
||||
import Data.Text.Lens
|
||||
import Data.Char
|
||||
import Data.Text (Text)
|
||||
|
||||
decode :: Text -> Text
|
||||
decode = unpacked . mapped %~ rot 13
|
||||
|
||||
rot :: Int -> Char -> Char
|
||||
rot n c | c >= 'a' && c <= 'z' = r 'a' 'z'
|
||||
| c >= 'A' && c <= 'Z' = r 'A' 'Z'
|
||||
| otherwise = c
|
||||
where
|
||||
r a b = chr $ ord a + ((ord c - ord a + n) `mod` (ord b - ord a + 1))
|
2
test/coverage/pkga/Setup.hs
Normal file
2
test/coverage/pkga/Setup.hs
Normal file
@ -0,0 +1,2 @@
|
||||
import Distribution.Simple
|
||||
main = defaultMain
|
26
test/coverage/pkga/pkga.cabal
Normal file
26
test/coverage/pkga/pkga.cabal
Normal file
@ -0,0 +1,26 @@
|
||||
cabal-version: 2.2
|
||||
-- Initial package description 'pkga.cabal' generated by 'cabal init'. For
|
||||
-- further documentation, see http://haskell.org/cabal/users-guide/
|
||||
|
||||
name: pkga
|
||||
version: 0.1.0.0
|
||||
-- synopsis:
|
||||
-- description:
|
||||
-- bug-reports:
|
||||
license: LicenseRef-PublicDomain
|
||||
author: Rodney Lorrimar
|
||||
maintainer: rodney.lorrimar@iohk.io
|
||||
category: Testing
|
||||
|
||||
library
|
||||
exposed-modules: PkgA
|
||||
build-depends: base
|
||||
, lens
|
||||
, text
|
||||
default-language: Haskell2010
|
||||
|
||||
executable pkga-exe
|
||||
main-is: MainA.hs
|
||||
build-depends: base
|
||||
hs-source-dirs: .
|
||||
default-language: Haskell2010
|
2
test/coverage/pkgb/Setup.hs
Normal file
2
test/coverage/pkgb/Setup.hs
Normal file
@ -0,0 +1,2 @@
|
||||
import Distribution.Simple
|
||||
main = defaultMain
|
10
test/coverage/pkgb/app/Main.hs
Normal file
10
test/coverage/pkgb/app/Main.hs
Normal file
@ -0,0 +1,10 @@
|
||||
module Main where
|
||||
|
||||
import ConduitExample (example)
|
||||
import PkgB (message)
|
||||
import qualified Data.Text.IO as T
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
T.putStrLn message
|
||||
example
|
7
test/coverage/pkgb/app/tests.hs
Normal file
7
test/coverage/pkgb/app/tests.hs
Normal file
@ -0,0 +1,7 @@
|
||||
module Main where
|
||||
|
||||
import System.Process
|
||||
import ConduitExample
|
||||
|
||||
main :: IO ()
|
||||
main = example
|
43
test/coverage/pkgb/pkgb.cabal
Normal file
43
test/coverage/pkgb/pkgb.cabal
Normal file
@ -0,0 +1,43 @@
|
||||
cabal-version: 2.2
|
||||
-- Initial package description 'pkgb.cabal' generated by 'cabal init'. For
|
||||
-- further documentation, see http://haskell.org/cabal/users-guide/
|
||||
|
||||
name: pkgb
|
||||
version: 0.1.0.0
|
||||
-- synopsis:
|
||||
-- description:
|
||||
-- bug-reports:
|
||||
license: LicenseRef-PublicDomain
|
||||
author: Rodney Lorrimar
|
||||
maintainer: rodney.lorrimar@iohk.io
|
||||
category: Testing
|
||||
|
||||
library
|
||||
exposed-modules: ConduitExample
|
||||
, PkgB
|
||||
build-depends: base
|
||||
, pkga
|
||||
, conduit
|
||||
, conduit-extra
|
||||
, directory
|
||||
, resourcet
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
|
||||
executable pkgb
|
||||
main-is: Main.hs
|
||||
build-depends: base
|
||||
, pkgb
|
||||
, optparse-applicative
|
||||
, text
|
||||
hs-source-dirs: app
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite tests
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: tests.hs
|
||||
hs-source-dirs: app
|
||||
build-depends: base
|
||||
, pkgb
|
||||
, process
|
||||
build-tools: pkga
|
21
test/coverage/pkgb/src/ConduitExample.hs
Normal file
21
test/coverage/pkgb/src/ConduitExample.hs
Normal file
@ -0,0 +1,21 @@
|
||||
-- https://github.com/snoyberg/conduit#readme
|
||||
|
||||
module ConduitExample (example) where
|
||||
|
||||
import Conduit
|
||||
import System.Directory (removeFile)
|
||||
|
||||
example = do
|
||||
-- Pure operations: summing numbers.
|
||||
print $ runConduitPure $ yieldMany [1..10] .| sumC
|
||||
|
||||
-- Exception safe file access: copy a file.
|
||||
writeFile "input.txt" "This is a test." -- create the source file
|
||||
runConduitRes $ sourceFileBS "input.txt" .| sinkFile "output.txt" -- actual copying
|
||||
readFile "output.txt" >>= putStrLn -- prove that it worked
|
||||
|
||||
-- Perform transformations.
|
||||
print $ runConduitPure $ yieldMany [1..10] .| mapC (+ 1) .| sinkList
|
||||
|
||||
removeFile "input.txt"
|
||||
removeFile "output.txt"
|
7
test/coverage/pkgb/src/PkgB.hs
Normal file
7
test/coverage/pkgb/src/PkgB.hs
Normal file
@ -0,0 +1,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module PkgB (message) where
|
||||
|
||||
import PkgA (decode)
|
||||
|
||||
message = decode "Guvf vf n pnony cebwrpg!"
|
5
test/coverage/pkgb/src/conduit-test.hs
Normal file
5
test/coverage/pkgb/src/conduit-test.hs
Normal file
@ -0,0 +1,5 @@
|
||||
module Main where
|
||||
|
||||
import ConduitExample
|
||||
|
||||
main = example
|
5
test/coverage/stack.yaml
Normal file
5
test/coverage/stack.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
resolver: lts-14.13
|
||||
|
||||
packages:
|
||||
- pkga/
|
||||
- pkgb/
|
@ -181,6 +181,7 @@ let
|
||||
hls-stack = callTest ./haskell-language-server/stack.nix { inherit compiler-nix-name; };
|
||||
cabal-hpack = callTest ./cabal-hpack { inherit util compiler-nix-name; };
|
||||
index-state = callTest ./index-state { inherit compiler-nix-name; };
|
||||
coverage = callTest ./coverage { inherit compiler-nix-name; };
|
||||
|
||||
unit = unitTests;
|
||||
} // lib.optionalAttrs (!stdenv.hostPlatform.isGhcjs && compiler-nix-name != "ghc8101" && compiler-nix-name != "ghc8102" ) {
|
||||
|
@ -26,7 +26,7 @@ in recurseIntoAttrs {
|
||||
|
||||
# fixme: run on target platform when cross-compiled
|
||||
printf "checking whether executable ran... " >& 2
|
||||
cat ${haskellLib.check packages.exe-only.components.exes.exe-only}
|
||||
cat ${haskellLib.check packages.exe-only.components.exes.exe-only}/test-stdout
|
||||
'' +
|
||||
# Aarch are statically linked and does not have ldd for these tests.
|
||||
optionalString (!stdenv.hostPlatform.isAarch32 && !stdenv.hostPlatform.isAarch64) (
|
||||
|
@ -41,7 +41,7 @@ in recurseIntoAttrs {
|
||||
printf "size of executable $exe is $size. \n" >& 2
|
||||
# fixme: run on target platform when cross-compiled
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.external-package-user.components.exes.external-package-user}
|
||||
cat ${haskellLib.check packages.external-package-user.components.exes.external-package-user}/test-stdout
|
||||
'' + (if stdenv.hostPlatform.isMusl
|
||||
then ''
|
||||
printf "checking that executable is statically linked... " >& 2
|
||||
|
@ -21,7 +21,7 @@ in recurseIntoAttrs {
|
||||
|
||||
buildCommand = ''
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.test-ghc-options.components.exes.test-ghc-options-exe}
|
||||
cat ${haskellLib.check packages.test-ghc-options.components.exes.test-ghc-options-exe}/test-stdout
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
@ -22,7 +22,7 @@ in recurseIntoAttrs {
|
||||
|
||||
buildCommand = ''
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.test-ghc-options.components.exes.test-ghc-options-exe}
|
||||
cat ${haskellLib.check packages.test-ghc-options.components.exes.test-ghc-options-exe}/test-stdout
|
||||
|
||||
echo '${concatStringsSep " " packageNames}' > $out
|
||||
'';
|
||||
|
@ -21,7 +21,7 @@ in recurseIntoAttrs {
|
||||
exe="${packages.test-project-flags.components.exes.test-project-flags-exe}/bin/test-project-flags-exe${stdenv.hostPlatform.extensions.executable}"
|
||||
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.test-project-flags.components.exes.test-project-flags-exe}
|
||||
cat ${haskellLib.check packages.test-project-flags.components.exes.test-project-flags-exe}/test-stdout
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
@ -19,7 +19,7 @@ in recurseIntoAttrs {
|
||||
exe="${packages.test-project-flags.components.exes.test-project-flags-exe}/bin/test-project-flags-exe${stdenv.hostPlatform.extensions.executable}"
|
||||
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.test-project-flags.components.exes.test-project-flags-exe}
|
||||
cat ${haskellLib.check packages.test-project-flags.components.exes.test-project-flags-exe}/test-stdout
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
@ -26,7 +26,7 @@ in recurseIntoAttrs {
|
||||
|
||||
# fixme: run on target platform when cross-compiled
|
||||
printf "checking whether executable runs... " >& 2
|
||||
cat ${haskellLib.check packages.sublib-docs.components.exes.sublib-docs}
|
||||
cat ${haskellLib.check packages.sublib-docs.components.exes.sublib-docs}/test-stdout
|
||||
|
||||
'' +
|
||||
# Musl and Aarch are statically linked..
|
||||
|
Loading…
Reference in New Issue
Block a user