bisect_ppx

Code coverage for OCaml
README

Bisect_ppx is a code coverage tool for OCaml and Reason. It helps you
test thoroughly by showing what's not tested.

You can browse the report seen above online here. The
details of how it is generated are in the worked example.

Table of contents

Usage

Dune

Refer to aantron/bisect-starter-dune, which produces
this report.

  1. Depend on Bisect_ppx
    in your opam file:

    depends: [
      "bisect_ppx" {dev & >= "2.5.0"}
      "dune" {>= "2.7.0"}
    ]
    
  2. Mark the code under test for preprocessing by
    bisect_ppx
    in your dune file:

    (library
     (public_name my_lib)
     (instrumentation (backend bisect_ppx)))
    
  3. Build and run your test binary. In addition to testing your code, when
    exiting, it will write one or more files with names like
    bisect0123456789.coverage:

    dune runtest --instrument-with bisect_ppx --force
    

    The --force flag forces all your tests to run, which is needed for an
    accurate coverage report.

    To run tests without coverage, do

    dune runtest
    
  4. Generate the coverage report in _coverage/index.html:

    bisect-ppx-report html
    

    You can also generate a short summary in the terminal:

    bisect-ppx-report summary
    

esy

Refer to aantron/bisect-starter-esy, which produces this
report
.

The instructions are the same as for regular Dune usage, but...

  1. Depend on Bisect_ppx in package.json,
    instead of in an opam file:

    "devDependencies": {
      "@opam/bisect_ppx": "^2.0.0",
    }
    
  2. Use the esy command for the build and for running binaries:

    esy install
    BISECT_ENABLE=yes esy dune runtest --force
    esy dune exec bisect-ppx-report -- html
    

BuckleScript

Refer to aantron/bisect-starter-bsb, which produces
this report.

  1. Depend on Bisect_ppx in package.json,
    and install it:

    "devDependencies": {
      "bisect_ppx": "^2.0.0"
    },
    "dependencies": {
      "bs-platform": "*"
    }
    
    npm install
    

    If you are using Yarn, you need to run an extra command because of
    yarnpkg/pkg#3421:

    yarn add bisect_ppx
    yarn --check-files
    

    If pre-built binaries aren't available for your system, the build will
    automatically fall back to building Bisect_ppx from source using
    esy, which will take a few minutes the first time. If this
    happens, you may need to install esy, if it is not already installed:

    npm install -g esy
    npm install
    
  2. Add Bisect_ppx to your bsconfig.json:

    "bs-dependencies": [
      "bisect_ppx"
    ],
    "ppx-flags": [
      "bisect_ppx/ppx"
    ]
    
  3. If you are using Jest, add this to your package.json:

    "setupFilesAfterEnv": ["bisect_ppx/lib/js/src/runtime/bucklescript/jest.js"]
    

    Or, if you have enabled the package-specs.in-source flag in
    bsconfig.json,

    "setupFilesAfterEnv": ["bisect_ppx/src/runtime/bucklescript/jest.js"]
    

    If the tests will be running in the browser, at the end of testing, call

    Bisect.Runtime.get_coverage_data();
    

    This returns binary coverage data in a string option, which you should
    upload or otherwise get out of the browser, and write into a .coverage
    file.

  4. Build in development with BISECT_ENABLE=yes, run tests, and generate the
    coverage report in _coverage/index.html:

    BISECT_ENABLE=yes npm run build
    npm run test
    npx bisect-ppx-report.exe html
    

    To exclude your test files from the report, change your PPX flags like so:

    "ppx-flags": [
      ["bisect_ppx/ppx", "--exclude-files", ".*test\\.re"]
    ]
    

    The last argument is a regular expression in the syntax of OCaml's Str
    module
    . Note
    that backslashes need to be escaped both inside the regular expression, and
    again because they are inside a JSON string.

    Multiple --exclude-files option can be specified if you want to provide
    multiple patterns.

  5. If your project uses both BuckleScript and native Dune, native Dune will
    start picking up OCaml files that are part of the BuckleScript bisect_ppx
    package. To prevent this, add a dune with the following contents to the
    root of your project:

    (data_only_dirs node_modules)
    

Js_of_ocaml

Refer to aantron/bisect-starter-jsoo, which produces
this report.

  1. Follow the Dune instructions above, except that the final test
    script must be linked with bisect_ppx.runtime

    (but not instrumented):

    (executable
     (name my_tester)
     (libraries bisect_ppx.runtime))
    
  2. If the tests will run on Node, call this function
    at the end of testing to write bisect0123456789.coverage:

    Bisect.Runtime.write_coverage_data ()
    

    If the tests will run in the browser, call

    Bisect.Runtime.get_coverage_data ()
    

    to get binary coverage data in a string option. Upload this string or
    otherwise extract it from the browser to create an .coverage file.

  3. Build the usual Js_of_ocaml target, including the instrumented code under
    test, then run the reporter to generate the coverage report in
    _coverage/index.html:

    BISECT_ENABLE=yes dune build my_tester.bc.js
    bisect-ppx-report html
    

Ocamlfind, Ocamlbuild, and OASIS

  • Ocamlbuild
    and OASIS
    instructions can be found at
    aantron/bisect_ppx-ocamlbuild.

  • With Ocamlfind, you must have your build script issue the right commands, to
    instrument the code under test, but not the tester:

    ocamlfind opt -package bisect_ppx -c src/source.ml
    ocamlfind opt -c test/test.ml
    ocamlfind opt -linkpkg -package bisect_ppx src/source.cmx test/test.cmx
    

    Running the tester will then produce bisect0123456789.coverage files,
    which you can process with bisect-ppx-report.

Sending to Coveralls

bisect-ppx-report can send reports to Coveralls and Codecov directly from
Travis, CircleCI, and GitHub Actions. To do this, run

bisect-ppx-report send-to Coveralls

or

bisect-ppx-report send-to Codecov

When sending specifically from GitHub Actions to Coveralls, use

- run: bisect-ppx-report send-to Coveralls
  env:
    COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    PULL_REQUEST_NUMBER: ${{ github.event.number }}

Put these commands in your CI script in the same place you would run
bisect-ppx-report html locally. See
bisect-ci-integration-megatest
for example CI scripts and current status of these integrations.

If you'd like Bisect_ppx to support other CI and/or coverage services, please
open an issue or send a pull request!

As a workaround for missing CI/coverage integrations, and for development,
bisect-ppx-report can also generate a JSON report in Coveralls format, which
can be uploaded to a service of your choice using a separate command. For
example, to send manually from Travis to Coveralls:

bisect-ppx-report \
  coveralls coverage.json \
  --service-name travis-ci \
  --service-job-id $TRAVIS_JOB_ID
curl -L -F json_file=@./coverage.json https://coveralls.io/api/v1/jobs

For other CI services, replace --service-name and --service-job-id as
follows:

| CI service | --service-name | --service-job-id |
| ---------- | ---------------- | -------------------- |
| Travis | travis-ci | $TRAVIS_JOB_ID |
| CircleCI | circleci | $CIRCLE_BUILD_NUM |
| Semaphore | semaphore | $REVISION |
| Jenkins | jenkins | $BUILD_ID |
| Codeship | codeship | $CI_BUILD_NUMBER |
| GitHub Actions | github | $GITHUB_RUN_NUMBER |

Note that Coveralls-style reports are less precise than the HTML reports
generated by Bisect_ppx, because Coveralls considers entire lines as visited or
not visited. Bisect_ppx instead considers individual expressions. There can be
many expressions on a single line, and the HTML report separately considers each
expression as visited or not visited.

Controlling coverage with [@coverage off]

You can tag expressions with [@coverage off], and neither they, nor their
subexpressions, will be instrumented by Bisect_ppx.

Likewise, you can tag module-level let-declarations with [@@coverage off],
and they won't be instrumented.

You can also turn off instrumentation for blocks of declarations inside a
module with [@@@coverage off] and [@@@coverage on].

Finally, you can exclude an entire file by putting [@@@coverage exclude_file]
into its top-level module. However, whenever possible, it is recommended to
exclude files by not preprocessing with Bisect_ppx to begin with.

Real-world example examined

Refer to:

The details:

  1. The project depeds on package bisect_ppx,
    so that Bisect_ppx is installed by opam pin --dev-repo markup and opam install .

  2. There are three libraries in src/, each set to have its
    sources preprocessed by Bisect_ppx:

    Because of the --conditional flag, preprocessing is enabled only when
    BISECT_ENABLE=yes is set in the environment, so it is off by default.

  3. A coverage build is triggered by running make coverage. This target...

    • Depends on make clean. This is a workaround until
      ocaml/dune#57 is solved. The problem is that doing a coverage
      build, after normal builds, should force all sources to be recompiled, so
      that they can be instrumented by the Bisect_ppx preprocessor. However, Dune
      doesn't know about this — it doesn't know that the behavior of the
      preprocessor depends on the BISECT_ENABLE environment variable.

      Indeed, the preprocessor shouldn't read this environment variable. The
      preprocessor should just be turned off by Dune when not building for
      coverage. However, Dune does not currently have the ability to
      conditionally turn off a preprocessor.

      In any case, to deal with this problem, the project always does a clean
      build when building for coverage.

    • Does a fresh build with BISECT_ENABLE=yes, causing the sources of the
      three libraries mentioned above to be instrumented.

    • Runs the test suite. bisect*.coverage files with coverage data are
      produced as a side effect.

    • Runs bisect-ppx-report to generate both the typical HTML report in
      _coverage/index.html, and also a textual summary in the terminal for very
      fast iteration.

  4. make coverage is also used in Travis
    to submit coverage reports to Coveralls. At the end of make coverage, the
    bisect*.coverage files are still present, so .travis.yml runs
    bisect-ppx-report again to generate the Coveralls report. This follows the
    Coveralls instructions exactly.

    Coveralls can be configured to leave comments
    about changes in coverage. It is usually configured to at least add an
    additional check to branches and PRs — see the "3 checks passed" in
    the hidden Details of the linked PR.

  5. During release, (preprocess (pps bisect_ppx)) is removed from all libraries that are being released. This is typically in a one-commit release branch off master, which is what ends up being tagged.

    This won't be necessary after ocaml/dune#57 is addressed.

Other topics

See advanced usage for:

  • Exhaustiveness checking.

  • Excluding generated files from coverage.

  • Environment variables.

Bisect_ppx users

A small sample of projects using Bisect_ppx:

Contributing

Bug reports and pull requests are warmly welcome. Bisect_ppx is developed on
GitHub, so please open an issue.

Bisect_ppx is developed mainly using opam. To get the latest development
version, run

opam source --dev-repo --pin bisect_ppx

You will now have a bisect_ppx subdirectory to work in. Try these Makefile
targets:

  • make test for unit tests.

  • make usage for build system integration tests, except BuckleScript.

  • make -C test/bucklescript full-test for BuckleScript. This requires NPM and
    esy.

Install
Published
20 Oct 2020
Sources
2.5.0.tar.gz
md5=2eaf2f24c5d91ff975050db37d8a4cd2
Dependencies
ounit2
with-test
ocamlfind
with-test
ocaml-migrate-parsetree
>= "1.7.0" & < "2.0.0"
ocaml
with-test & < "4.12"
ocaml
>= "4.02.0"
dune
>= "2.7.0"
cmdliner
>= "1.0.0"
Reverse Dependencies